<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Dcycle Blog</title>
    <description>Drupal automated tests, continuous integration</description>
    <link>http://blog.dcycle.com/</link>
    <atom:link href="http://blog.dcycle.com/planet.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 04 May 2026 17:43:52 +0000</pubDate>
    <lastBuildDate>Mon, 04 May 2026 17:43:52 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      
      <item>
        <title>Verifying telephone numbers in Drupal</title>
        <description>&lt;p&gt;When allowing users to input phone numbers in Drupal, you might want to make sure that they actually have access to the phone number they are using, rather than being allowed to input any random phone number.&lt;/p&gt;

&lt;p&gt;In practice, when a user claims to have access to a phone number, we want to design a system that sends a one-time code to the user by SMS, and for the user to enter that code on our site, telling us that the user’s phone number has been verified as belonging to them (or, rather, that they have access to incoming SMS messages).&lt;/p&gt;

&lt;p&gt;In this article we will choose an SMS provider, introduce a series of modules which allow phone number verification, and make sure we store our provider API keys in a relatively secure way.&lt;/p&gt;

&lt;h2 id=&quot;choosing-an-sms-framework-gateway&quot;&gt;Choosing an SMS Framework gateway&lt;/h2&gt;

&lt;p&gt;You will need to set up an account with a third-party provider of SMS services; this requires setting up an account and getting an API key, potentially a phone number and other API information.&lt;/p&gt;

&lt;p&gt;Providers that can currently integrate into Drupal’s &lt;a href=&quot;https://www.drupal.org/project/smsframework&quot;&gt;SMS Framework (smsframework)&lt;/a&gt; (which we will will use) &lt;a href=&quot;https://www.drupal.org/node/2641028&quot;&gt;are listed on the Gateways for SMS Framework page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are several, and I have only tested a single one, which works for me. However this article is not an indorsement of a particular service. You are encouraged to do your own research and find the one which is best for you.&lt;/p&gt;

&lt;p&gt;In this example, we will use &lt;a href=&quot;https://www.twilio.com&quot;&gt;Twilio&lt;/a&gt;. If you want to follow along, start by opening an account there and getting the following information at &lt;a href=&quot;https://console.twilio.com/&quot;&gt;https://console.twilio.com/&lt;/a&gt; in the “account info” section:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Account SID&lt;/li&gt;
  &lt;li&gt;Auth token&lt;/li&gt;
  &lt;li&gt;From number (this is a phone number in the format +15555555555)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;We will install and enable the following modules to set up phone number verification:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/mobile_number&quot;&gt;Mobile Number (mobile_number)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/smsframework&quot;&gt;SMS Framework (smsframework)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/sms_twilio&quot;&gt;Twilio SMS Integration (sms_twilio)&lt;/a&gt; &lt;em&gt;or the module corresponding to whichever provider you have chosen&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assuming you have a brand new working Drupal site and you opened an account with Twilio, you can install your modules in the standard Drupal way. In my tests SMS has a dependency on &lt;a href=&quot;https://www.drupal.org/project/dynamic_entity_reference&quot;&gt;Dynamic Entity Reference (dynamic_entity_reference)&lt;/a&gt; which needs to be explicitly required:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;composer require \
  drupal/mobile_number \
  drupal/sms_twilio \
  drupal/sms:^2.1@beta \
  drupal/dynamic_entity_reference
drush en sms_twilio sms mobile_number sms_sendtophone -y
drush cr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;field-configuration&quot;&gt;Field Configuration&lt;/h2&gt;

&lt;p&gt;In this example we will add a Phone Number field to user profile pages; this can be done by logging in as user 1 (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush uli&lt;/code&gt; will give you a link), going to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/admin/config/people/accounts/fields/add-field&lt;/code&gt;, selecting a new field of type “Mobile Number” (&lt;strong&gt;not&lt;/strong&gt; “Telephone number”) with the label “Phone Number” (machine name field_phone_number),&lt;/p&gt;

&lt;p&gt;In the field settings page, you can check “Yes, only verified numbers” in the “Unique” section, then click “Save field settings”.&lt;/p&gt;

&lt;p&gt;In the following page, you can select “Required” in the “Verification” section.&lt;/p&gt;

&lt;p&gt;Finally click “Save settings”.&lt;/p&gt;

&lt;h2 id=&quot;sms-gateway-configuration&quot;&gt;SMS gateway configuration&lt;/h2&gt;

&lt;p&gt;We now need to tell Mobile Number to use the SMS gateway (and in our example Twilio) to send SMS messages to phone numbers.&lt;/p&gt;

&lt;p&gt;Visit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/admin/config/smsframework/gateways/add&lt;/code&gt; and select the gateway “Twilio”. Name it “My Gateway” (&lt;strong&gt;the name is important for the “Securing Twilio API information” step, below&lt;/strong&gt;), then save. New fields will appear allowing you to enter API information from Twilio. &lt;strong&gt;I recommend not entering the information here for security purposes.&lt;/strong&gt; In the “Account SID”, “Auth token” and “From number” fields, you can enter “See unversioned settings.php”.&lt;/p&gt;

&lt;p&gt;Save your settings.&lt;/p&gt;

&lt;h2 id=&quot;securing-twilio-api-information-by-keeping-it-out-of-version-control&quot;&gt;Securing Twilio API information by keeping it out of version control&lt;/h2&gt;

&lt;p&gt;Any API information is sensitive. I think these should never be in code, or in your database.&lt;/p&gt;

&lt;p&gt;If they’re in code, any person with even read-only access to your codebase; or indeed any person with access to any of your continuous integration platforms, will have immediate access to your full Twilio account.&lt;/p&gt;

&lt;p&gt;If the API info is in your database, you run the risk of future developers doing a non-sanitized database dump of your production database for local development for an unrelated feature, then leaving that database lying around on their non-encrypted computer, which they will eventually forget at a Dunkin’ Donuts.&lt;/p&gt;

&lt;p&gt;As with any sensitive information, these should be in in an unversioned settings file or environment variable. In this example we’ll use an unversioned settings file.&lt;/p&gt;

&lt;p&gt;Depending on your setup, this can be either the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./sites/default/settings.php&lt;/code&gt; file itself, which is often unversioned. If you are using a codebase where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./sites/default/settings.php&lt;/code&gt; is versioned, such as Acquia, you might want to include reference to a separate unversioned file therein, something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if (file_exists(&apos;/path/to/unversioned/directory/unversioned.php&apos;)) {
  require(&apos;/path/to/unversioned/directory/local-settings/unversioned.php&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We use such an approach in the &lt;a href=&quot;https://github.com/dcycle/starterkit-drupalsite&quot;&gt;Dcycle Drupal Starterkit project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So in your unversioned code, you can enter this information:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$config[&apos;sms.gateway.my_gateway&apos;][&apos;settings&apos;] = [
  &apos;account_sid&apos; =&amp;gt; &apos;MY_ACCOUNT_SID&apos;,
  &apos;auth_token&apos; =&amp;gt; &apos;MY_AUTH_TOKEN&apos;,
  &apos;from&apos; =&amp;gt; &apos;+15555555555&apos;,
];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Of course, enter your own information instead of the dummy information provided in the example.)&lt;/p&gt;

&lt;h2 id=&quot;setting-the-fallback-gateway&quot;&gt;Setting the fallback gateway&lt;/h2&gt;

&lt;p&gt;Now go to /admin/config/smsframework/settings and set the fallback logger to My Gateway, and save.&lt;/p&gt;

&lt;h2 id=&quot;testing-the-system&quot;&gt;Testing the system&lt;/h2&gt;

&lt;p&gt;Now you can go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/user/1/edit&lt;/code&gt; and enter your phone number in the Phone Number field, the click “Send verification code”.&lt;/p&gt;

&lt;p&gt;If you receive a verification code by SMS on your phone, congratulations! Otherwise, happy debugging!&lt;/p&gt;

&lt;h2 id=&quot;caveat-administrators-always-bypass-phone-number-verification&quot;&gt;Caveat: administrators always bypass phone number verification&lt;/h2&gt;

&lt;p&gt;Mobile Number defines a permission allowing certain roles to bypass verification; and since administrators, including user 1, always have all permissions, all administrators will be able to enter unverified phone numbers anywhere.&lt;/p&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/mobile_number&quot;&gt;Mobile Number (mobile_number)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/smsframework&quot;&gt;SMS Framework (smsframework)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/node/2641028&quot;&gt;Gateways for SMS Framework page, Drupal.org, last updated on 5 June 2021&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.twilio.com&quot;&gt;Twilio&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/sms_twilio&quot;&gt;Twilio SMS Integration (sms_twilio)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        
          <description>&lt;p&gt;When allowing users to input phone numbers in Drupal, you might want to make sure that they actually have access to the phone number they are using, rather than being allowed to input any random phone number.&lt;/p&gt;

</description>
        
        <pubDate>Tue, 19 Apr 2022 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2022-04-19/phone-verification-drupal/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2022-04-19/phone-verification-drupal/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Translating a Drupal user interface (PHP and Javascript): a workflow</title>
        <description>
&lt;p&gt;So you find yourself, as one does, building a complete user interface with PHP and Javascript for Drupal 9; and you’d like to delegate the translation of the front-end to a non-developer. (You want the client to be empowered to change the UI strings in English or any other language; you &lt;em&gt;don’t&lt;/em&gt; want to be a bottleneck for string translations).&lt;/p&gt;

&lt;h3 id=&quot;three-types-of-translations&quot;&gt;Three types of translations&lt;/h3&gt;

&lt;p&gt;Translations can be done in PHP, like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;use Drupal\Core\StringTranslation\StringTranslationTrait;

class MyClass {

  use StringTranslationTrait;

  function foo() {
    return $this-&amp;gt;t(&apos;Hello, @n&apos;, [
      &apos;@n&apos; =&amp;gt; &apos;world&apos;,
    ]);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;They can also be done in JavaScript, like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
function foo() {
  return Drupal.t(&apos;Hello @n&apos;, {
    &apos;@n&apos;: &apos;world&apos;,
  });
}
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally you can translate strings directly in twig template files, like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
&amp;lt;div&amp;gt;{{ &quot;Hello World&quot; | trans }}&amp;lt;/div&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;starting-from-the-beginning&quot;&gt;Starting from the beginning&lt;/h3&gt;

&lt;p&gt;Let’s take a concrete example: we will provide code for a very simple application which shows a pseudo-dashboard.&lt;/p&gt;

&lt;p&gt;If you start with a standard Drupal installation, and an empty module named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_custom_module&lt;/code&gt;, here is some convoluted code which uses the three types of translations above to achieve a dashboard which does nothing useful:&lt;/p&gt;

&lt;p&gt;./my_custom_module.routing.yml:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;my_custom_module.dashboard:
  path: &apos;/my-dashboard&apos;
  defaults:
    _controller: &apos;\Drupal\my_custom_module\Controller\MyDashboard::content&apos;
  requirements:
    _permission: &apos;administer site configuration&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;./my_custom_module.libraries.yml&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;current_time:
  js:
    current_time.js: {}
  dependencies:
    - core/jquery
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;./my_custom_module.module&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php
function my_custom_module_theme() {
  return [
    &apos;my_custom_module_dashboard&apos; =&amp;gt; [
      &apos;template&apos; =&amp;gt; &apos;dashboard&apos;,
      &apos;variables&apos; =&amp;gt; [
        &apos;admin_page_link&apos; =&amp;gt; &apos;&apos;,
      ],
    ],
  ];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;./templates/dashboard.html.twig&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{{ attach_library(&apos;my_custom_module/current_time&apos;) }}
&amp;lt;h3&amp;gt;{{ &quot;Welcome to your Dashboard&quot; | trans }}&amp;lt;h3&amp;gt;
&amp;lt;div&amp;gt;
  &amp;lt;div&amp;gt;{{ admin_page_link }}&amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;current-time&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;./current_time.js:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(function ($, Drupal, drupalSettings) {
  Drupal.behaviors.MyCustomModuleCurrentTime = {
    attach: function (context, settings) {
      $(&apos;.current-time&apos;).html(Drupal.t(&apos;This page was generated on @t&apos;, {
        &apos;@t&apos;: Date(),
      }));
    }
  };
})(jQuery, Drupal, drupalSettings);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;./src/Controller/MyDashboard.php&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php

namespace Drupal\my_custom_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\Core\Link;

class MyDashboard extends ControllerBase {

  use StringTranslationTrait;

  public function content() {
    $return = [
      &apos;#theme&apos; =&amp;gt; &apos;my_custom_module_dashboard&apos;,
      // Don&apos;t ask me why making links is so complicated in Drupal.
      &apos;#admin_page_link&apos; =&amp;gt; Link::fromTextAndUrl($this-&amp;gt;t(&apos;Go to admin&apos;), Url::fromRoute(&apos;system.admin&apos;))-&amp;gt;toString(),
    ];

    return $return;
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;enabling-your-custom-module-and-some-translation-specific-modules&quot;&gt;Enabling your custom module and some translation-specific modules:&lt;/h3&gt;

&lt;p&gt;Once your module has an info file (read up on how to build a custom module if you’re not sure how), you can enable your custom module and the config_translation module:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en -y config_translation my_custom_module
chown www-data:www-data sites/default/files/translations
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;adding-a-language&quot;&gt;Adding a language&lt;/h3&gt;

&lt;p&gt;For this demo we’ll add French. To do that,&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Go to /admin/config/regional/language&lt;/li&gt;
  &lt;li&gt;Select Add Language, then French. This should update Drupal for French.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;making-english-strings-overridable&quot;&gt;Making English strings overridable&lt;/h3&gt;

&lt;p&gt;Base English strings are hard-coded, but administrators can override them. This will free your time for development. To enable admins to override strings:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Go to /admin/config/regional/language/edit/en&lt;/li&gt;
  &lt;li&gt;Check “Enable interface translation to English”, then save.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;lets-visit-our-dashboard&quot;&gt;Let’s visit our dashboard&lt;/h2&gt;

&lt;p&gt;If you have correctly created your my_custom_module, above, you will be able to log in as user, visit /my-dashboard, and see the following:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Welcome to your Dashboard&lt;br /&gt;
Go to admin&lt;br /&gt;
This page was generated on Sun Feb 27 2022 23:11:08 GMT-0500 (EST)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This dashboard is useless, except in that it demonstrates how to provide translatable strings in PHP, Javascript, and Twig. The point is to show that regardless how you code your translatable strings (PHP, Javascript, or Twig), your string can be translated and overridden in the administrative Drupal interface.&lt;/p&gt;

&lt;h2 id=&quot;translating-our-strings-to-french&quot;&gt;Translating our strings to French&lt;/h2&gt;

&lt;p&gt;So let’s see if we can use a nice admin interface to translate our strings to French. Go to /admin/config/regional/translate?langcode=fr, and in the search box, enter:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Welcome to your Dashboard&lt;/li&gt;
  &lt;li&gt;Go to admin&lt;/li&gt;
  &lt;li&gt;This page was generated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Translate each of those to French:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Bienvenue à votre tableau de bord&lt;/li&gt;
  &lt;li&gt;Allez à la page admin&lt;/li&gt;
  &lt;li&gt;Cette page a été générée le @t&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is that this is something non-developers can do with a bit of training (especially for string parameters such as ‘@t’).&lt;/p&gt;

&lt;h2 id=&quot;testing-the-french-version&quot;&gt;Testing the French version&lt;/h2&gt;

&lt;p&gt;Visit:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;/fr/my-dashboard&lt;/li&gt;
  &lt;li&gt;/my-dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will now see the dashboard in both French and English.&lt;/p&gt;

&lt;h2 id=&quot;overriding-english-strings&quot;&gt;Overriding English strings&lt;/h2&gt;

&lt;p&gt;It can be inefficient to modify the underlying English strings in code every time an English string is modified. So we can also “translate” English strings to English. Let’s say you don’t like the base strings, you can translate them, in English, to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Welcome to your great Dashboard&lt;/li&gt;
  &lt;li&gt;Go to the admin page&lt;/li&gt;
  &lt;li&gt;This page was viewed on @t&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news is that this can be done exactly the same way for English as for non-English languages:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;go to /admin/config/regional/translate?langcode=en&lt;/li&gt;
  &lt;li&gt;search for the base strings&lt;/li&gt;
  &lt;li&gt;override them&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;empowering-non-developers-to-translate-strings-a-time-saver&quot;&gt;Empowering non-developers to translate strings: a time-saver&lt;/h2&gt;

&lt;p&gt;I have found that most clients will send unversioned Excel files or (ugh!) Word documents with string modification or translation requests. Dealing with these is time-consuming for developers, and expensive for clients.&lt;/p&gt;

&lt;p&gt;After a 30-minute traning on how to translate strings, with hyperlinks to the string-translation pages on a wiki perhaps, clients are empowered to do their own translation, and developers are not distracted.&lt;/p&gt;

&lt;p&gt;An added bonus of string overrides is that you can write your end-to-end tests based on unchanging strings, &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/9/tests/browser-tests/testLogInAndEdit.js&quot;&gt;for example here we are asserting that the string “Log In” is on the /user page&lt;/a&gt;, regardless of whether your site-editors decide to change, say, “Log In” to “Log in” (with a lower-case i) or whatever else.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;
</description>
        
          <description>
</description>
        
        <pubDate>Sun, 27 Feb 2022 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2022-02-27/drupal-translation-workflow/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2022-02-27/drupal-translation-workflow/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Altering a list view in drupal, using an example with Webform</title>
        <description>&lt;p&gt;In some cases administrative lists are actually views using the core Views module, as is the case with /admin/content (which can be modified using the Views interface at /admin/structure/views/view/content). These are quite easy to modify in a development environment, export as config, then import in a production environment. How to do so is outside the scope of this article, though.&lt;/p&gt;

&lt;p&gt;However, certain administrative views do not use the Views module; rather they use an entity’s list view, which requires some coding to modify.&lt;/p&gt;

&lt;p&gt;For example the &lt;a href=&quot;https://www.drupal.org/project/webform&quot;&gt;Webform&lt;/a&gt; module does not use Views to build its administrative list of webforms. So how can we alter it?&lt;/p&gt;

&lt;p&gt;Follow along, this article will show you how!&lt;/p&gt;

&lt;h3 id=&quot;basic-setup&quot;&gt;Basic setup&lt;/h3&gt;

&lt;p&gt;In our example we will use &lt;a href=&quot;https://www.drupal.org/project/webform&quot;&gt;Webform&lt;/a&gt; 6.1.2 on Drupal 9.3.4. The general idea should apply regardless of the version though.&lt;/p&gt;

&lt;p&gt;We will start with a standard installation along with webform_ui and webform.&lt;/p&gt;

&lt;h3 id=&quot;our-goal&quot;&gt;Our goal&lt;/h3&gt;

&lt;p&gt;Our goal will be to add a column to /admin/structure/webform showing the date of last submission, if any.&lt;/p&gt;

&lt;h3 id=&quot;how-the-webform-administrative-list-works&quot;&gt;How the Webform administrative list works&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://git.drupalcode.org/project/webform/-/blob/6.1.2/webform.routing.yml#L96-102&quot;&gt;webform.routing.yml&lt;/a&gt; defines the /admin/structure/webform path as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;entity.webform.collection:
  path: &apos;/admin/structure/webform&apos;
  defaults:
    _entity_list: &apos;webform&apos;
    _title: &apos;Webforms&apos;
  requirements:
    _custom_access: &apos;\Drupal\webform\Access\WebformAccountAccess::checkOverviewAccess&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This means we are using the Webform entity’s list view to display a list of webforms. In turn, the Webform entity &lt;a href=&quot;https://git.drupalcode.org/project/webform/-/blob/6.1.2/src/Entity/Webform.php#L53&quot;&gt;defines its list_builder as \Drupal\webform\WebformEntityListBuilder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code list builder itself is at &lt;a href=&quot;https://git.drupalcode.org/project/webform/-/blob/6.1.2/src/WebformEntityListBuilder.php&quot;&gt;./src/WebformEntityListBuilder.php&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://git.drupalcode.org/project/webform/-/blob/6.1.2/src/WebformEntityListBuilder.php#L219-362&quot;&gt;buildHeader() and buildRow()&lt;/a&gt; methods are what need to overridden if we want to add a column.&lt;/p&gt;

&lt;h3 id=&quot;creating-our-subclass-of-drupalwebformwebformentitylistbuilder&quot;&gt;Creating our subclass of \Drupal\webform\WebformEntityListBuilder&lt;/h3&gt;

&lt;p&gt;THe first thing we need to do is create a subclass of WebformEntityListBuilder. We can create our own custom module, and in it, at ./my_custom_module/src/MyCustomWebformEntityListBuilder.php, create our class, overriding WebformEntityListBuilder’s buildHeader() and buildRow(). We are injecting the ‘date.formatter’ service so we can format dates nicely the Drupal way.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php
# ./my_custom_module/src/MyCustomWebformEntityListBuilder.php

namespace Drupal\my_custom_module;

use Drupal\webform\WebformEntityListBuilder;
use Drupal\webform\Entity\WebformSubmission;
use Drupal\Core\Entity\EntityInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeInterface;

/**
 * Custom webform list with last submission.
 *
 * See https://blog.dcycle.com/blog/2022-02-07/alter-list-view/.
 */
class MyCustomWebformEntityListBuilder extends WebformEntityListBuilder {

  /**
   * Injected date formatter.
   *
   * @var \Drupal\Core\Datetime\DateFormatterInterface
   */
  protected $dateFormatter;

  /**
   * {@inheritdoc}
   */
  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
    $instance = parent::createInstance($container, $entity_type);

    $instance-&amp;gt;dateFormatter = $container-&amp;gt;get(&apos;date.formatter&apos;);

    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function buildHeader() {
    return parent::buildHeader() + [
      &apos;last_submission&apos; =&amp;gt; [
        &apos;data&apos; =&amp;gt; $this-&amp;gt;t(&apos;Latest submission&apos;),
        &apos;specifier&apos; =&amp;gt; &apos;latest&apos;,
      ],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildRow(EntityInterface $entity) {
    return parent::buildRow($entity) + [
      &apos;last_submission&apos; =&amp;gt; $this-&amp;gt;lastSubmission($entity),
    ];
  }

  /**
   * Get the last submission for a webform.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   A webform.
   *
   * @return string
   *   Either a formatted date, or the translated string &quot;No submission&quot;.
   */
  public function lastSubmission(EntityInterface $entity) : string {
    if ($entity-&amp;gt;isResultsDisabled()) {
      return $this-&amp;gt;t(&apos;Results disabled (external)&apos;);
    }
    else {
      $query = $this-&amp;gt;submissionStorage
        -&amp;gt;getQuery()
        -&amp;gt;accessCheck(TRUE)
        -&amp;gt;condition(&apos;webform_id&apos;, $entity-&amp;gt;id())
        -&amp;gt;sort(&apos;completed&apos;, &apos;DESC&apos;)
        -&amp;gt;range(0, 1);

      $results = $query-&amp;gt;execute();

      if ($row = array_pop($results)) {
        $submission = WebformSubmission::load(intval($row));
        return $this-&amp;gt;dateFormatter-&amp;gt;format($submission-&amp;gt;completed-&amp;gt;value);
      }
    }
    return $this-&amp;gt;t(&apos;No submission&apos;);
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;altering-the-list-builder-for-webforms&quot;&gt;Altering the list builder for webforms&lt;/h3&gt;

&lt;p&gt;According to &lt;a href=&quot;https://drupal.stackexchange.com/a/192813/13414&quot;&gt;this DrupalAnswers thread&lt;/a&gt; we can alter the list builder for webform by adding this hook to our .module file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php
# ./my_custom_module/my_custom_module.module

/**
 * Implements hook_entity_type_alter().
 *
 * See https://drupal.stackexchange.com/a/192813/13414.
 */
function my_custom_module_entity_type_alter(array &amp;amp;$entity_types) {
  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  $entity_types[&apos;webform&apos;]-&amp;gt;setListBuilderClass(&apos;Drupal\my_custom_module\MyCustomWebformEntityListBuilder&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You will now see the latest submission in a brand new column in the list of webforms.&lt;/p&gt;

&lt;h3 id=&quot;next-steps-sorting&quot;&gt;Next steps: sorting&lt;/h3&gt;

&lt;p&gt;I still cannot figure out how to sort the webforms by their last submission date. If anyone manages to do that, leave a comment in the comments thread.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;In some cases administrative lists are actually views using the core Views module, as is the case with /admin/content (which can be modified using the Views interface at /admin/structure/views/view/content). These are quite easy to modify in a development environment, export as config, then import in a production environment. How to do so is outside the scope of this article, though.&lt;/p&gt;

</description>
        
        <pubDate>Mon, 07 Feb 2022 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2022-02-07/alter-list-view/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2022-02-07/alter-list-view/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Docker PHP on the M1 chip, example with Static Analysis on Drupal: 9 times faster</title>
        <description>&lt;p&gt;In 2020, Apple unveiled a new chip, M1, which uses a different architecture than the Intel chips widely used in servers and laptops.&lt;/p&gt;

&lt;p&gt;Docker calls the intel architecture “linux/amd64”, and the M1 architecture “linux/arm64” (can also be linux/arm64/v7, linux/arm64/v8, etc.).&lt;/p&gt;

&lt;p&gt;We will attempt to look at a speed test for a typical Dockerized PHP processs.&lt;/p&gt;

&lt;h2 id=&quot;our-test&quot;&gt;Our test&lt;/h2&gt;

&lt;p&gt;I have used a &lt;a href=&quot;https://github.com/dcycle/docker-phpstan-drupal&quot;&gt;Dockerized version of PHPStan for Drupal&lt;/a&gt; that I am maintaining, in order to run the tests.&lt;/p&gt;

&lt;p&gt;The goal of this project is to perform static analysis of PHP code, making sure it has an internal logic, without actually running the code (I think of it as a lazy person’s automated testing).&lt;/p&gt;

&lt;p&gt;I use Jenkins to rebuild this Docker image weekly to make sure it is always up-to-date. My Jenkins job uses the DigitalOcean API to create a new virtual machine, then uses the technique described by Artur Klauser in his article &lt;a href=&quot;https://medium.com/@artur.klauser/building-multi-architecture-docker-images-with-buildx-27d80f7e2408&quot;&gt;Building Multi-Architecture Docker Images With Buildx, Artur Klauser, Medium, Jan 18, 2020&lt;/a&gt; to create a multi-architecture image. &lt;a href=&quot;https://github.com/dcycle/prepare-docker-buildx&quot;&gt;I have also created a GitHub project which helps set this up on the VM&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The image is available on the &lt;a href=&quot;https://hub.docker.com/r/dcycle/phpstan-drupal/tags?page=1&amp;amp;ordering=last_updated&quot;&gt;Docker Hub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On a weekly basis, I push a tag 4 which is always the latest version, I also push a tag with the day’s date and time, for example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the tag “4.2021-10-21-20-15-31-UTC” only has the linux/amd64 architecture&lt;/li&gt;
  &lt;li&gt;the tag “4.2021-11-17-13-45-26-UTC” has both linux/amd64 and linux/arm64.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our test consists of running the static analysis agains the &lt;em&gt;node&lt;/em&gt; module, part of the Drupal project.&lt;/p&gt;

&lt;p&gt;We have run our tests on the latest version of Docker Desktop:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;on a mid-2014 dual-core Intel i7 chip Macbook Pro;&lt;/li&gt;
  &lt;li&gt;and on a 2021 M1 Max MacBook Pro.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In both cases, we allocated 10 Gb RAM to Docker; for our Intel mac, we allocate 2 CPUs; and on M1 we allocated 5 CPUs.&lt;/p&gt;

&lt;h2 id=&quot;results-using-emulation&quot;&gt;Results using emulation&lt;/h2&gt;

&lt;p&gt;We will start by using a Docker image built only for the linux/amd64 architecture, forcing our M1 Mac to use emulation:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull dcycle/phpstan-drupal:4.2021-10-21-20-15-31-UTC
time docker run --rm dcycle/phpstan-drupal:4.2021-10-21-20-15-31-UTC /var/www/html/core/modules/node --memory-limit=-1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On our 2014 Intel Macbook, we get 1:27.258; and on M1, we get 1:25.16, not a speed increase you’d write to your mother about (not that she’d understand if you did).&lt;/p&gt;

&lt;p&gt;In this case M1 warns us:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;WARNING: The requested image&apos;s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(For these tests, we’re not interested in the actual static analysis test results, although there are some interesting tidbits in there; you can see you this can be useful for your own projects.)&lt;/p&gt;

&lt;h2 id=&quot;results-with-a-native-arm-image&quot;&gt;Results with a native ARM image&lt;/h2&gt;

&lt;p&gt;Recall that the tag “4.2021-11-17-13-45-26-UTC” was optimized for both Intel and M1 chips.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull dcycle/phpstan-drupal:4.2021-11-17-13-45-26-UTC
time docker run --rm dcycle/phpstan-drupal:4.2021-11-17-13-45-26-UTC /var/www/html/core/modules/node --memory-limit=-1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we have a more eyebrow-rising speed increase: &lt;strong&gt;9.782 seconds for the M1&lt;/strong&gt;. Unsurprisingly, the above has very little variation on the Intel chip.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;If you’re using PHP Docker images in emulation module, there is virtually no speed increase between a 2014 Intel-based Macbook Pro and and a M1 Max-based Macbook Pro.&lt;/p&gt;

&lt;p&gt;However, if you invest in using M1-optimized images, &lt;a href=&quot;https://github.com/dcycle/docker-phpstan-drupal/commit/70324881392f34d24f7f8620e7f2cc72f424e1ee&quot;&gt;like I did with my PHPStan Drupal image&lt;/a&gt;, you can reap very interesting speed increases for your PHP, and other, Docker jobs.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;In 2020, Apple unveiled a new chip, M1, which uses a different architecture than the Intel chips widely used in servers and laptops.&lt;/p&gt;

</description>
        
        <pubDate>Wed, 17 Nov 2021 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2021-11-17/m1-docker-php-speed-test/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2021-11-17/m1-docker-php-speed-test/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>hook_update_N(), a powerful and dangerous tool to use sparingly</title>
        <description>&lt;h2 id=&quot;what-is-hook_update_n&quot;&gt;What is hook_update_N()?&lt;/h2&gt;

&lt;p&gt;Let’s say you are developing a Drupal module (custom or contrib) which tracks how many visitors landed on specific node pages, version 1 of your code might track visitors by nid (node id) in the database using a table like this:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;nid&lt;/th&gt;
      &lt;th&gt;visitors&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt;22&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Let’s set aside the debate over whether the above is a good idea or not, but once your code has been deployed live to production sites, that’s what the data will look like.&lt;/p&gt;

&lt;p&gt;This module might work very well for a long time, and then you might have the need to track not only nodes but also, say, taxonomy term pages. You might reengineer to look like this:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;type&lt;/th&gt;
      &lt;th&gt;id&lt;/th&gt;
      &lt;th&gt;visitors&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;node&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;node&lt;/td&gt;
      &lt;td&gt;13&lt;/td&gt;
      &lt;td&gt;22&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;term&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;16&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;To achieve this change when the first version of your database is already out in the wild, you need to tell target environments to update the database schema. This is done using hook_update_N(), and you would replace the N() by incremental numbers, something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Update database schema to allow for terms, not only nodes.
 */
function hook_update_9001() {
  ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If this case 9 is the major version (Drupal 9) and 001 because this is the first update to your code.&lt;/p&gt;

&lt;p&gt;Each module tracks which version it’s using, so that if code introduces new hook_update_N() functions, it will know to run them only once. You can tell which schema version any installed module is using by running, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush ev &quot;print(drupal_get_installed_schema_version(&apos;webform&apos;))&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This might tell you, for example, that Webform’s current schema is 8621. This means that the latest update hook that was run is &lt;a href=&quot;https://git.drupalcode.org/project/webform/-/blob/6.x/includes/webform.install.update.inc&quot;&gt;Webform’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_8621()&lt;/code&gt;&lt;/a&gt;. If the codebase introduces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_8622()&lt;/code&gt;, say, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_8640()&lt;/code&gt; (you can skip numbers if need to), then the database will be marked as out of date and running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb&lt;/code&gt; will run the the new hook and update the installed schema version.&lt;/p&gt;

&lt;p&gt;If you ever need to re-run an update hook (which happens rather rarely), you can update the schema, like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush ev &quot;drupal_set_installed_schema_version(&apos;webform&apos;, 8620)&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;so-whats-wrong-with-this&quot;&gt;So what’s wrong with this?&lt;/h2&gt;

&lt;p&gt;This works well almost all the time, and you can automate your deployment process to update the database, making sure your schemas are always in sync. However as developers and site users it is important to be aware of certain drawbacks of hook_update_N(), which I’ll get to in detail:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; tightly couples the database to the code version;&lt;/li&gt;
  &lt;li&gt;it makes gradual-deployment on multi-container setups such as Kubernetes fragile (or impossible);&lt;/li&gt;
  &lt;li&gt;rollbacks are not possible;&lt;/li&gt;
  &lt;li&gt;it can add considerable compexity to deployment of configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-shaky-foundation-of-database-driven-websites&quot;&gt;The shaky foundation of database-driven websites&lt;/h2&gt;

&lt;p&gt;The idea of version control is paramount to how we conceive of computer code. If you’re following the precepts of continuous deployment, then every version of your code needs to “work” (that is, tests need to pass, or, at the very least, it needs to be installable).&lt;/p&gt;

&lt;p&gt;For example, let’s assume a bug makes it to your production envrionment for version 5 of your code, and you know this bug was not present on version 4 of your code, you should theoretically be able to check out version 4 and confirm it was working, then figure out what the difference it between version 4 and 5.&lt;/p&gt;

&lt;p&gt;In fact this is exactly how things work on static sites such as Jekyll: all your data &lt;em&gt;and&lt;/em&gt; your functionality (Javascript) are in your codebase. Each version of your code will be internally coherent, and not rely on an external unversioned database to do something useful.&lt;/p&gt;

&lt;p&gt;On database-driven projects based on Drupal or Wordpress, if you check out version 4 of your codebase, it will probably not do anything useful without a database dump &lt;em&gt;which was created using version 4 of your code&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Therefore, although we all use version control for our code, in a way we are fooling ourselves, because critical parts of our project are not version-controlled: the database dump, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./sites/default/files&lt;/code&gt; folder, and the private files folder.&lt;/p&gt;

&lt;p&gt;Although it makes sense for certain elements to be a database or on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./sites/default/files&lt;/code&gt;, for example, an encrypted user account password or a user’s avatar; for other elements such as your “About page” text, it would really make a lot more sense for this to be under version control.&lt;/p&gt;

&lt;p&gt;In fact, the blog post you are reading right now is a &lt;a href=&quot;https://github.com/alberto56/dcycleblog/edit/master/_posts/2021-01-29-hook-update-n.md&quot;&gt;file under version control on Jekyll, which you can see using this link&lt;/a&gt;, and not some collection of opaque, unversioned, entries in database tables with names like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node__body&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node__field_tags&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_field_revision&lt;/code&gt;, which can be changed at a moment’s notice by any module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; functions.&lt;/p&gt;

&lt;p&gt;Oh, did I mention that I love Drupal?&lt;/p&gt;

&lt;h2 id=&quot;tight-code-database-coupling&quot;&gt;Tight code-database coupling&lt;/h2&gt;

&lt;p&gt;Let’s imagine a world where the database schema never changed. A world where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; does not even exist.&lt;/p&gt;

&lt;p&gt;In such a world, you could take any version of your code, and any version of your database dump (say, the latest version), combine the two on a test environment, and debug errors at will.&lt;/p&gt;

&lt;p&gt;In the real world, every time any module updates the database schema, it makes the database more tightly coupled to the current version of the codebase.&lt;/p&gt;

&lt;p&gt;Let’s take our “number of visitors per entity” code we had earlier: if I use an old codebase which expects my table to contain fields “nid” and “visitors”, but my only available database dump has fields “type” “id”, “visitors”, the history of my carefully version-controlled codebase will be useless, and old versions will fail with an error such as:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ERROR 1054 (42S22): Unknown column &apos;id&apos; in &apos;field list&apos;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;gradual-deployments&quot;&gt;Gradual deployments&lt;/h2&gt;

&lt;p&gt;Mostly we think of Drupal sites as being on a server with one copy of the codebase, and one copy of the database. So the concept of keeping the database and code “in sync” makes sense.&lt;/p&gt;

&lt;p&gt;But as more and more teams use containers and Kubernetes-type container-orchestration systems, high-traffic sites might have, say, one performance-optimized database, and then 5, 10 or 20 load-balanced copies of your PHP code.&lt;/p&gt;

&lt;p&gt;Acquia uses such a setup behind the scenes for its cloud hosting, so it’s good to develop with this in mind. On Acquia’s setup, all PHP container use a single, shared database, as well as shared private and public files directories.&lt;/p&gt;

&lt;p&gt;But the PHP containers &lt;em&gt;do not&lt;/em&gt; share the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp&lt;/code&gt; directory. This means that every time you perform a web request on a server, the load balancer might direct you to a container with its own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp&lt;/code&gt;, whose contents differ from other containers’ &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It’s important to realize this if your code stuff such as building large files over several web requests, and can lead to hard-to-diagnose bugs such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/webform/issues/2980276&quot;&gt;#2980276 Webform assumes the /tmp directory is always the same, but if there are multiple servers, each may have its own /tmp directory&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/csv_importer/issues/3170504&quot;&gt;#3170504 On high-availabilities setups with multiple containers, the /tmp directory might differ between calls, make the error message more descriptive&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But, in addition to providing you with headaches such as the above issues, multiple containers can also allow you to do &lt;strong&gt;gradual deployments of new code&lt;/strong&gt;, reducing the cost of potential failure.&lt;/p&gt;

&lt;p&gt;For example, let’s say you have 20 Drupal containers with 20 copies of your codebase, and each Drupal container is connected to a shared database, and shared files and private files directories. If you are deploying a risky update to your code, you might want to start by deploying it to 25% of the containers (5). Then if there are no adverse effects, scale up to 10 the next day, then the entire 20 the day after.&lt;/p&gt;

&lt;p&gt;Code that uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; can break this workflow: because all containers share the database, if container 1 has the new version of your code and updates the database accordingly (so that the new database fields are “type” “id”, “visitors”); then container 10 (which uses the old version of your code) will fail when it looks up the database field “nid”.&lt;/p&gt;

&lt;h2 id=&quot;rollbacks&quot;&gt;Rollbacks&lt;/h2&gt;

&lt;p&gt;Let’s forget about fancy container orchestration and just look at a typical Drupal website. A simple real-world site might have a “contact us” webform and some pages, plus some custom functionality.&lt;/p&gt;

&lt;p&gt;Let’s say you are deploying a change to your codebase which triggers a hook_update_N(). No matter the amount of unit tests and testing on stage, there is always the possibility that a deployment to production might trigger unforseen issues. Let’s assume this is the case here.&lt;/p&gt;

&lt;p&gt;A typical deployment-to-production scenario would be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You backup your production database.&lt;/li&gt;
  &lt;li&gt;You install your new code.&lt;/li&gt;
  &lt;li&gt;You run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb&lt;/code&gt; which updates the database schema based on your hook_update_N().&lt;/li&gt;
  &lt;li&gt;A few hours pass. Several people fill in your contact form, which means now your database backup from step 1 is out of date.&lt;/li&gt;
  &lt;li&gt;You realize your newly-deployed code breaks something which was not caught by your stage testing or your automated tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a situation like this, if you did not have hook_update_N()s in your code, you could simply roll back your codebase on production to the previous version.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;this is no longer an option&lt;/strong&gt; because your database will not work with previous versions of your codebase: there is no hook_downgrade_N(). You are now forced to live with the latest version of your code, and all the benefits of version-controlling your code are for naught.&lt;/p&gt;

&lt;h2 id=&quot;config-management&quot;&gt;Config management&lt;/h2&gt;

&lt;p&gt;Let us recall the elements which make up a Drupal website:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Versioned code.&lt;/li&gt;
  &lt;li&gt;Unversioned database and file directories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are using configuration management and a dev-stage-production workflow, there is a third category:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Configuration, including the list enabled modules, defined node types and fields&lt;/strong&gt;, which exist both in the database &lt;em&gt;and&lt;/em&gt; in unversioned code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is worth recalling a typical workflow:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;add field_new_field to the article node type on your local machine.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;the field is now in your local development database but not in your codebase&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;drush config-export&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;the field is now in your local development database and also in your codebase&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;do all your testing and push your code to production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point your field is in your production codebase &lt;em&gt;but not&lt;/em&gt; your production database.&lt;/p&gt;

&lt;p&gt;You probably have a deployment script which includes a “drush updb” step. The question is: do you run “drush config-import” &lt;em&gt;before&lt;/em&gt; or &lt;em&gt;after&lt;/em&gt; “drush updb”?&lt;/p&gt;

&lt;p&gt;It turns out this is not that easy a question to answer. (Drush also provides a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush deploy&lt;/code&gt; command which combines configuration import and database updates.)&lt;/p&gt;

&lt;p&gt;Regardless of your deployment process, however, we need to take into account a more troubling possibility:&lt;/p&gt;

&lt;p&gt;In addition to relatively benign database schema updates, hook_update_N() &lt;strong&gt;can modify configuration as well&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In such a case, if you are not careful to run &lt;strong&gt;hook_update_N()&lt;/strong&gt; first on your development environment, then &lt;strong&gt;export the resulting configuration&lt;/strong&gt;, then run your deployment, you may run into the following problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.drupal.org/project/drupal/issues/3110362&quot;&gt;#3110362 If an update hook modifies configuration, then old configuration is imported, the changes made by the update hook are forever lost.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s look at a real-world example using the Webform module. Let’s install a new Drupal 8 site with Webform 5.23, then export our configuration, then upgrade to Webform 6.x and import our old configuration. We’ll look at the kind of headache this can lead to (note to beginners: &lt;strong&gt;do not&lt;/strong&gt; do this on a production site, it will completely erase your database).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;composer require drupal/webform:5.23
drush site-install -y
drush en webform_ui -y
drush config-export -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This puts your current site configuration into code. Among said configuration, let’s focus on a single piece of configuration from Webform:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush config:get webform.settings settings.default_page_base_path
# &apos;webform.settings:settings.default_page_base_path&apos;: form
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The base path for webforms is “form”. This tells Webform to build URLs with a structure such as https://example.com/form/whatever.&lt;/p&gt;

&lt;p&gt;Let’s now update webform, and our database.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;composer require drupal/webform:6
drush updb -y
drush config-import -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In &lt;a href=&quot;https://git.drupalcode.org/project/webform/-/blob/6.x/includes/webform.install.update.inc&quot;&gt;Webform’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webform_update_8602()&lt;/code&gt;&lt;/a&gt;, the config item webform.settings:settings.default_page_base_path is changed from “form” to “/form”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But we are re-importing old config, which overwrites this change and reverts webform.settings:settings.default_page_base_path to “form”, not “/form”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To see the type of hard-to-diagnose error to which this might lead, you can now log into your Drupal site, visit /admin/structure/webform, create a webform named “test”, and click on the “View” tab.&lt;/p&gt;

&lt;p&gt;Because the base path lack the expected leading prefix, you now get the “not found” URL /admin/structure/webform/manage/form/test, instead of the expected /form/test – a critical bug if you are on a production site.&lt;/p&gt;

&lt;p&gt;In addition, this has a number of cascading effects including the creation of badly-formatted URL aliases which you can see at /admin/config/search/path.&lt;/p&gt;

&lt;p&gt;If you find yourself in this situation on production, you need to revert your Webform schema version on your development environment, export your config, reimport it on production, and resave your forms, and potentially fix all your paths starting with “form” on /admin/config/search/path so that they start with “/form”.&lt;/p&gt;

&lt;p&gt;To be fair, this is not the fault of the Webform maintainers. In my opinion it shows a fundamental frailty in hook_update_N() combined with lack of documentation on deployment best practices. However, if we strive for Drupal to be a robust framework, there should not be a single point of failure (in this case not strictly adhering to fickle, badly-documented deployment procedures) which can lead to major instability on production.&lt;/p&gt;

&lt;h2 id=&quot;how-do-we-fix-hook_update_n&quot;&gt;How do we fix hook_update_N()?&lt;/h2&gt;

&lt;p&gt;Here are a few approaches to avoid the potential damage done by hook_update_N():&lt;/p&gt;

&lt;h3 id=&quot;approach-1-dont-use-hook_update_n&quot;&gt;Approach 1: don’t use hook_update_N()&lt;/h3&gt;

&lt;p&gt;When possible, you might consider not using hook_update_N() at all. Consider our “number of visitors per node” module from earlier.&lt;/p&gt;

&lt;p&gt;Instead of a hook_udate_N(), your code could do something like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Do not change the field name from “nid” to “id”. Even though “id” makes more sense, the field is called “nid”, just leave it at that.&lt;/li&gt;
  &lt;li&gt;Do not expect there to be a “type” field. If your code needs it, for example if creating an entry for the first visitor to a non-node entity, your code can create it.&lt;/li&gt;
  &lt;li&gt;Assume an empty “type” means you are dealing with a node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above approach adds complexity to your code, which you can add to a “storage” abstraction class. Although not ideal, this does away with the need to use hook_update_N().&lt;/p&gt;

&lt;h3 id=&quot;approach-2-dont-use-hook_update_n-to-update-configuration&quot;&gt;Approach 2: Don’t use hook_update_N() to update configuration&lt;/h3&gt;

&lt;p&gt;Updating configuration, as seen previously, is even more dangerous than updating non-configuration database tables. So if at all possible, avoid it.&lt;/p&gt;

&lt;p&gt;In the Webform example given above, it might have been reasonable to consider keeping with the old non-leading-slash format for path prefixes, rather than update configuration.&lt;/p&gt;

&lt;p&gt;When you absolutely must update configuration, you could consider the possibility that certain users might have reimported old configuration, and provide error-checking and hook_requirements() (displaying error messages on the /admin/reports/status page) accordingly.&lt;/p&gt;

&lt;h3 id=&quot;approach-3-robust-exception-handling&quot;&gt;Approach 3: Robust exception handling&lt;/h3&gt;

&lt;p&gt;Do not assume that your database schema, or your configuration structure, is up-to-date. If you decide to provide a hook_update_N() to update the schema from, for example, “nid” and “visitors” to “type”, “id”, “visitors”, when querying the database, you might want to consider the possibility that for whatever reason the database is not up-to-date. Here is some pseudo-code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public function num_visitors_for_entity($id, $type = &apos;node&apos;) : int {
  try {
    return $this-&amp;gt;query_database($type, $id);
  }
  catch (\Exception $e) {
    $this-&amp;gt;logAndDisplayException($e);
    return 0;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That way, if your database and code are not in sync, it’s not going to break your entire site, but rather log an exception and fail gracefully.&lt;/p&gt;

&lt;h3 id=&quot;approach-4-keep-config-changing-logic-idempotent-and-separate-from-update-hooks&quot;&gt;Approach 4: keep config changing logic idempotent and separate from update hooks&lt;/h3&gt;

&lt;p&gt;Let’s look again at &lt;a href=&quot;https://git.drupalcode.org/project/webform/-/blob/6.x/includes/webform.install.update.inc&quot;&gt;Webform’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webform_update_8602()&lt;/code&gt;&lt;/a&gt;, the config item webform.settings:settings.default_page_base_path is changed from “form” to “/form”.&lt;/p&gt;

&lt;p&gt;I would recommend having a separate function to update config, and call that function from the update hook. That way, if a development team makes the mistake of not updating their configuration before importing it into production, it will become easier to run, say “my_module_update_configuration()”.&lt;/p&gt;

&lt;p&gt;Then, your hook_requirements() might perform some sanity checks to make sure your configuration is as expected (in this example, that the “webform.settings:settings.default_page_base_path” config item has a leading slash). If this smoke test fails, developers can be directed to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_module_update_configuration()&lt;/code&gt; which will update all configuration to the required state.&lt;/p&gt;

&lt;p&gt;In addition, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;my_module_update_configuration()&lt;/code&gt; can be made idempotent, meaning: no matter how often you run it, you will always end up with the desired state, and never get an error.&lt;/p&gt;

&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Extension%21module.api.php/function/hook_update_N/8.2.x&quot;&gt;hook_update_N() API documentation on drupal.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        
          <description>&lt;h2 id=&quot;what-is-hook_update_n&quot;&gt;What is hook_update_N()?&lt;/h2&gt;

</description>
        
        <pubDate>Fri, 29 Jan 2021 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2021-01-29/hook_update_n/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2021-01-29/hook_update_n/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Adding continuous integration (CI) to your workflow</title>
        <description>&lt;p&gt;This post is aimed at web development teams and is not tied to a specific technology. We will aim to not get more technical than is needed, but rather to explore what Continuous integration (CI) is, and how it can help save teams money within a month of it being set up.&lt;/p&gt;

&lt;h2 id=&quot;what-is-continuous-integration&quot;&gt;What is continuous integration?&lt;/h2&gt;

&lt;p&gt;Although several definitions of CI have been proposed, we will use the following definition in the context of this post:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Cotinuous integration (CI) is the practice of running any number of tests, automatically, on a project, periodically and/or whenever the code changes. For CI practitioners, the number one priority is for tests to always be passing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;a-simple-example-please&quot;&gt;A simple example, please&lt;/h2&gt;

&lt;p&gt;Here is the very simplest example I can think of:&lt;/p&gt;

&lt;p&gt;Let’s say you’re maintaining an old-school HTML website (no fancy stuff like databases or PHP), your team may decide to set up CI to make sure a file called “index.html” exists in your codebase: if it exists, your test passes; if it is absent, your test fails.&lt;/p&gt;

&lt;p&gt;Checks may be run every time your code is changed.&lt;/p&gt;

&lt;p&gt;Your team might store code on GitHub, and link a cloud CI provider such as CircleCI to your codebase, having it trigger every time your code changes.&lt;/p&gt;

&lt;p&gt;You will then define a script which is your definition of “what it means for your your codebase to pass”: checking for the existence of “index.html” is a one-line script.&lt;/p&gt;

&lt;h2 id=&quot;a-more-complex-example&quot;&gt;A more complex example&lt;/h2&gt;

&lt;p&gt;Although the example above has value, it is very simple, and you may soon find yourself wanting to add higher-value tests to your script. This ability to add complexity over time is a powerful feature of CI: getting started is simple, and you can add as many tests as you want over time depending on your available resources.&lt;/p&gt;

&lt;p&gt;Let’s say your team is maintaining a Drupal or Wordpress codebase with lots of complex code, your team may set up a CI server that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;checks for broken links on the live environment every so often;&lt;/li&gt;
  &lt;li&gt;checks every few minutes that the live environment is responding and has some expected keywords on its front page;&lt;/li&gt;
  &lt;li&gt;every so often, checks that the live environment adheres to certain Accessibility standards;&lt;/li&gt;
  &lt;li&gt;every so often, checks that the live environment is not reporting any errors;&lt;/li&gt;
  &lt;li&gt;on every code change, perform some static analysis on custom PHP code: for example, that a function which expects an array as an argument is never called with a string.&lt;/li&gt;
  &lt;li&gt;on every code change, make sure PHP code adheres to coding standards (for example, functions should have comments; and indenting should be correct).&lt;/li&gt;
  &lt;li&gt;on every code change, create a dummy Drupal or Wordpress site with a dummy database and make sure your site fires up, and run some end-to-end tests against it.&lt;/li&gt;
  &lt;li&gt;etc., etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A cloud-based tool such as CircleCI can work well to check the codebase when it is changed; and a hosted tool such as Jenkins might be a good fit for running periodic checks (such as a sanity check making sure the production environment works).&lt;/p&gt;

&lt;p&gt;The above example corresponds to real-world checks I perform on lost of projects I maintain; and both CircleCI and Jenkins are tools I have been using for years.&lt;/p&gt;

&lt;h2 id=&quot;so-how-much-does-all-this-cost&quot;&gt;So how much does all this cost?&lt;/h2&gt;

&lt;p&gt;“How much does this cost?” is actually the wrong question. “How much can I save?” is a better way of putting it. Consider the following graph, the horizontal axis is time, and the vertical axis is cumulative project cost.&lt;/p&gt;

&lt;script src=&quot;https://d3js.org/d3.v4.js&quot;&gt;&lt;/script&gt;

&lt;!-- Create a div where the graph will take place --&gt;
&lt;div style=&quot;background: ; position: relative;&quot;&gt;
  &lt;div id=&quot;ci&quot;&gt;&lt;/div&gt;

  &lt;script&gt;

  // set the dimensions and margins of the graph
  var margin = {top: 10, right: 30, bottom: 30, left: 60},
      width = 460 - margin.left - margin.right,
      height = 400 - margin.top - margin.bottom;

  // append the svg object to the body of the page
  var svg = d3.select(&quot;#ci&quot;)
    .append(&quot;svg&quot;)
      .attr(&quot;width&quot;, width + margin.left + margin.right)
      .attr(&quot;height&quot;, height + margin.top + margin.bottom)
    .append(&quot;g&quot;)
      .attr(&quot;transform&quot;,
            &quot;translate(&quot; + margin.left + &quot;,&quot; + margin.top + &quot;)&quot;);

  //Read the data
  d3.csv(&quot;/data/posts/2021-01-13/ci.csv&quot;, function(data) {

    // group the data: I want to draw one line per group
    var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
      .key(function(d) { return d.name;})
      .entries(data);

    // Add X axis --&gt; it is a date format
    var x = d3.scaleLinear()
      .domain(d3.extent(data, function(d) { return d.year; }))
      .range([ 0, width ]);
    svg.append(&quot;g&quot;)
      .attr(&quot;transform&quot;, &quot;translate(0,&quot; + height + &quot;)&quot;)
      .call(d3.axisBottom(x).ticks(0).tickFormat(() =&gt; &quot;&quot;));

    // Add Y axis
    var y = d3.scaleLinear()
      .domain([0, d3.max(data, function(d) { return +d.n; })])
      .range([ height, 0 ]);
    svg.append(&quot;g&quot;)
      .call(d3.axisLeft(y).ticks(0).tickFormat(() =&gt; &quot;&quot;));

    // color palette
    var res = sumstat.map(function(d){ return d.key }) // list of group names
    var color = d3.scaleOrdinal()
      .domain(res)
      .range([&apos;#e41a1c&apos;,&apos;#377eb8&apos;,&apos;#4daf4a&apos;,&apos;#984ea3&apos;,&apos;#ff7f00&apos;,&apos;#ffff33&apos;,&apos;#a65628&apos;,&apos;#f781bf&apos;,&apos;#999999&apos;])

    // Draw the line
    svg.selectAll(&quot;.line&quot;)
        .data(sumstat)
        .enter()
        .append(&quot;path&quot;)
          .attr(&quot;fill&quot;, &quot;none&quot;)
          .attr(&quot;stroke&quot;, function(d){ return color(d.key) })
          .attr(&quot;stroke-width&quot;, 1.5)
          .attr(&quot;d&quot;, function(d){
            return d3.line()
              .x(function(d) { return x(d.year); })
              .y(function(d) { return y(+d.n); })
              .curve(d3.curveMonotoneX)
              (d.values)
          })
  })

  // https://stackoverflow.com/questions/19787925
  d3.svg.axis().tickValues([])


  &lt;/script&gt;
&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;The red line is business as usual: because we are not maintaining CI scripts or setting up tests, the up-front cost is low. But eventually you’ll lose control of your codebase and spend all your time putting out fires (I call this the “technical debt wall”).&lt;/li&gt;
  &lt;li&gt;The blue line is the CI approach, higher up-front cost to set things up, but eventually you’ll get less errors.&lt;/li&gt;
  &lt;li&gt;Where the two lines intersect, I call the “sweet spot”. That’s when you start saving money. Your “sweet spot” is not months or years away: I firmly believe it should happen within a month. If it takes longer than a month, you’re overengineering your CI system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;so-what-are-these-up-front-costs&quot;&gt;So what are these up-front costs?&lt;/h2&gt;

&lt;p&gt;The up-front costs are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Creating a simple script which defines what it means for your code “to work”. If you find this intimidating, just have your script check for a file that must be present, as in the simple example presented earlier.&lt;/li&gt;
  &lt;li&gt;Make sure your code is tracked in GitHub or BitBucket.&lt;/li&gt;
  &lt;li&gt;Make sure your entire team accepts the principle that making tests pass is the number one priority. This is crucial. If you start accepting failing tests, then CI becomes a useless burden. This also means every member of your team must agree with every test that is performed. &lt;strong&gt;If a test is not important enough to warrant dropping everything when it fails, then you should not have that test in your codebase.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Integrate a simple, free CI cloud provider like CircleCI and make sure it works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of the above, together, can take between an hour and a day.&lt;/p&gt;

&lt;h2 id=&quot;how-about-the-ongoing-costs&quot;&gt;How about the ongoing costs?&lt;/h2&gt;

&lt;p&gt;Ongoing costs are closely relate to the complexity of your CI setup. If you are just testing for an “index.html” file, your ongoing costs are close to zero, but may include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;dealing with errors and updates in the CI script itself. Don’t forget the CI script is computer code, and like any computer code, it needs to be maintained.&lt;/li&gt;
  &lt;li&gt;updating the CI script to deal with API changes in the cloud CI provider.&lt;/li&gt;
  &lt;li&gt;fixing false negatives. For example, someone may change the filename from index.html to index.htm, which might require you to fix your test script to also test for index.htm in addition to index.html.&lt;/li&gt;
  &lt;li&gt;onboarding new team members to understand the importance of making sure tests always are passing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your tests are super simple (such as checking that an “index.html” file exists), the above costs are low, probably less than one hour a month.&lt;/p&gt;

&lt;p&gt;If your tests are complex (as in our second example, above), you might set aside 5 to 10 hours a month for ongoing costs.&lt;/p&gt;

&lt;p&gt;Obviously, if your ongoing costs are higher than your savings, then you are “over-testing”.&lt;/p&gt;

&lt;h2 id=&quot;so-what-are-the-benefits&quot;&gt;So what are the benefits?&lt;/h2&gt;

&lt;p&gt;The fundamental trick of CI is to keep your benefits higher than your costs. Let’s go back to our simple “index.html” example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We have already established that there are minimal up-front and ongoing costs.&lt;/li&gt;
  &lt;li&gt;There are also ongoing savings: once you know that your index.html file is guaranteed to exist, your manual testing time decreases.&lt;/li&gt;
  &lt;li&gt;The cost in lost revenue, lost confidence, and debugging time in case someone accidentally deletes index.html from your website would be considerable high.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on the above, you can conclude whether it’s worth implementing CI.&lt;/p&gt;

&lt;h2 id=&quot;continuous-improvement-of-your-ci-setup&quot;&gt;Continuous improvement of your CI setup&lt;/h2&gt;

&lt;p&gt;Checking for “index.html” is probably of very low value, but once you’ve done that, you’ve also set up the foundation to improve your script. Every time you feel your CI script has a positive cost-benefit ratio, it is time to improve your CI script. In practice, I have found that in projects under active development, the CI setup gets constantly improved.&lt;/p&gt;

&lt;p&gt;Specifically, any time a problem makes its way to production, it should be a gut reaction to introduce a fix, along with a test to make sure the problem never happens again.&lt;/p&gt;

&lt;p&gt;The key is making incremental improvements, making sure your cost-benefit ratio is always positive.&lt;/p&gt;

&lt;h2 id=&quot;docker-and-containerization&quot;&gt;Docker and containerization&lt;/h2&gt;

&lt;p&gt;Docker, and containerization generally, embed software and configuration in computer code along with your project code.&lt;/p&gt;

&lt;p&gt;The widespread adoption of Docker and containerization in recent years has been crucial for CI. Without containerization, let’s say you want to run PHP static analysis, start a database with a Drupal site, run end-to-end tests, you need to install a bunch of software on your CI server (or your laptop), make sure the versions and configuration are in sync with your local development setups. This is simply too expensive.&lt;/p&gt;

&lt;p&gt;Docker makes all this easy: simply put, Docker abstracts all the software and configuration, making software act the same on any computer that has Docker installed.&lt;/p&gt;

&lt;p&gt;If you are not using Docker and you’d like to see how simple this makes things, install and launch Docker Desktop on your computer, give it 6Gb RAM instead of the default 2Gb in its preferences, then you’ll be able to run all tests on my Drupal Starterkit project, without any additional fiddling with configuration of software:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ~/Desktop &amp;amp;&amp;amp; git clone https://github.com/dcycle/starterkit-drupalsite.git
cd starterkit-drupalsite
./scripts/ci.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It should take about 10 minutes to run all tests and &lt;em&gt;it will not add any software to your computer; everything is done on throwaway “containers”&lt;/em&gt;. (In general, tests become a lot more frustrating to developers as they take longer to run; which is why I have a policy of not accepting tests which take more than 20 minutes to run.)&lt;/p&gt;

&lt;p&gt;The amount of software packages and configuration required to run all the tests in this example is enormous: database servers and configuration, passwords, permissions, PHPUnit, the right version of PHP and Apache or Nginx…; however it’s all defined in Docker files and in code, not on host computers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which is why you can run the tests in three lines of code!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This makes it possible to run these complex tests on your computer without installing any software other than Docker.&lt;/p&gt;

&lt;p&gt;This also makes it possible to run these exact tests, &lt;em&gt;sans&lt;/em&gt; extra configuration, on CircleCI or other CI providers which support virtual machines with Docker preinstalled. In fact, that’s exactly what we’re doing with the Drupal Starterkit. CircleCI even provides a cute badge to indicate whether tests are passing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Click on the badge below to see test results on CircleCI, which should be identical to the results on your computer if you ran the the above script (you’ll need to log in with your GitHub or BitBucket account).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://circleci.com/gh/dcycle/starterkit-drupalsite/tree/9&quot;&gt;&lt;img src=&quot;https://circleci.com/gh/dcycle/starterkit-drupalsite/tree/9.svg?style=svg&quot; alt=&quot;CircleCI&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;

&lt;p&gt;Whether you are using a cloud service such as CircleCI, or hosting your own CI server with Jenkins or other software, be aware that it adds a potential attack vector for hackers, especially because by design, CI software needs access to your codebase.&lt;/p&gt;

&lt;p&gt;In early 2021, a vulnerability was discovered in JetBrains TeamCity (&lt;a href=&quot;https://www.nytimes.com/2021/01/06/us/politics/russia-cyber-hack.html&quot;&gt;Widely Used Software Company May Be Entry Point for Huge U.S. Hacking, New York Times, January 6th, 2021&lt;/a&gt;) in relation to the major SolarWinds hack.&lt;/p&gt;

&lt;p&gt;Make sure you have a solid security policy, including the Principle of Least Privilege (POLP) and other industry-standard security approaches; also make sure your codebase, even if it’s private, does not contain any sensitive data, including API keys.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;With continuous integration (CI), you can let computers do the grunt work of looking for bugs in your codebase, liberating your developers to do more productive work, reducing the number of bugs that make it into production, and increasing the level of confidence of all stakeholders in your software, and deploying frequently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And, above all, saving money.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CI can be as simple or as complex as you need: start small, then let your CI process grow as your team becomes more comfortable with it.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;This post is aimed at web development teams and is not tied to a specific technology. We will aim to not get more technical than is needed, but rather to explore what Continuous integration (CI) is, and how it can help save teams money within a month of it being set up.&lt;/p&gt;

</description>
        
        <pubDate>Wed, 20 Jan 2021 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2021-01-20/ci/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2021-01-20/ci/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Deploying Drupal to Kubernetes, no previous knowledge required</title>
        <description>&lt;p&gt;Kubernetes is a way of deploying resilient, scalable applications to the cloud.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Resilient&lt;/strong&gt; because Kubernetes is designed to recover if something goes wrong.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scalable&lt;/strong&gt; because with Kubernetes, your application is not linked to a single virtual machine (VM), but rather to a &lt;strong&gt;cluster&lt;/strong&gt; of VMs which you can scale up or down transparently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What Kubernetes is &lt;em&gt;not&lt;/em&gt; is a magic bullet. Before investing too much in Kubernetes, you are encouraged to read &lt;a href=&quot;https://pythonspeed.com/articles/dont-need-kubernetes/&quot;&gt;“Let’s use Kubernetes!” Now you have 8 problems, by Itamar Turner-Trauring, Python Speed, March 4th, 2020&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this article, we will create a Kubernetes cluster and deploy a minimum viable Drupal installation to it, with the following features (this list will be our success criteria at the end of this article):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Minimal vendor lock-in&lt;/strong&gt;: we will avoid vendor-specific resources such as database and volume storage where possible, prefering our own containers.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Deployment of Drupal alongside other applications&lt;/strong&gt;: we will deploy applications other than Drupal to demonstrate how your Drupal app can coexist nicely on a Kubernetes cluster.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Secret management&lt;/strong&gt;: Your Drupal application probably has &lt;em&gt;secrets&lt;/em&gt;: environment-specific information such as API keys, or database passwords which should not be in the codebase. We will see how to manage these in Kubernetes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;LetsEncrypt&lt;/strong&gt;: We will serve our different cluster applications via HTTPS using an Nginx reverse proxy, with set-it-and-forget-it automatic certificate renewals.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Volumes&lt;/strong&gt;: Our Kubernetes applications will store their data in &lt;em&gt;volumes&lt;/em&gt; which can be backed up. In the case of Drupal, the MySQL database and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/sites/default/files&lt;/code&gt; directory will be on volumes. All application code will be on containers, as we will see later.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Automation of incremental deployments&lt;/strong&gt;: deployment should generally be as automated as possible; most modern applications see deployments to production several times daily. &lt;em&gt;In the context of this tutorial we are not recommending Kubernetes on production just yet, but rather to serve development environments; the performance and security concerns related to Kubernetes on production are outside the scope of this article, and frankly at the time of this writing I haven’t yet used Kubernetes on production myself.&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Easy local development&lt;/strong&gt;: although having a local version of Kubernetes is possible, it can make your laptop really, really hot. We will use Docker and docker-compose rather than Kubernetes to develop our code locally.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Branch staging environments&lt;/strong&gt;: we will spin up environments per GitHub branch and destroy the environments when the branch gets deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that I haven’t gotten into the jargon of Kubernetes: nodes, pods, deployments, services; for me this has taken a while to get my head around, so my approach in this article will be to introduce concepts only as we need them. You can always refer to the &lt;strong&gt;glossary&lt;/strong&gt; at the end of this article if you’d like quick definitions.&lt;/p&gt;

&lt;p&gt;This tutorial is presented in several sections for your convenience:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/01-setup&quot;&gt;Setup&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/02-create-cluster&quot;&gt;Creating a cluster&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/03-api-not-gui&quot;&gt;API, not GUI&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/04-latest-yaml&quot;&gt;Getting the latest Kubernetes YAML file&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/05-interacting&quot;&gt;Interacting with Kubernetes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/06-helm&quot;&gt;Introducing Helm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/07-helm-on-docker&quot;&gt;Helm on Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/08-drupal-helm&quot;&gt;Install Drupal via Helm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/09-ingress&quot;&gt;Using a reverse-proxy ingress&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/10-configure-reverse-proxy&quot;&gt;Configuring the reverse proxy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/11-letsencrypt&quot;&gt;Letsencrypt&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/12-customize-helm-template&quot;&gt;Customizing the Helm template&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/13-secure-wildcard-subdomains&quot;&gt;Secure wildcard subdomains&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/14-custom-docker-images&quot;&gt;Custom Docker images&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/15-jenkins&quot;&gt;Jenkins&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/16-next-steps&quot;&gt;Next Steps&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/glossary&quot;&gt;Glossary&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/kubernetes/resources&quot;&gt;Resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        
          <description>&lt;p&gt;Kubernetes is a way of deploying resilient, scalable applications to the cloud.&lt;/p&gt;

</description>
        
        <pubDate>Wed, 22 Apr 2020 14:27:32 +0000</pubDate>
        <link>http://blog.dcycle.com/kubernetes/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/kubernetes/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Start unit testing your Drupal and other PHP code today</title>
        <description>&lt;p&gt;Unit tests are the fastest, most reliable kinds of tests: they confirm that the smallest &lt;em&gt;units&lt;/em&gt; of your code, i.e. class methods, work as expected.&lt;/p&gt;

&lt;p&gt;Unit tests do not require a full environment with a database and external libraries; this makes unit tests extremely fast.&lt;/p&gt;

&lt;p&gt;In this article we will look at how to take any PHP code – a Drupal site or module, or indeed any other PHP codebase unrelated to Drupal – and start unit testing it &lt;em&gt;today&lt;/em&gt;. We’ll start by setting up tests which work for any PHP code, and then we’ll see how to run your tests on the Drupal testbot if you so desire.&lt;/p&gt;

&lt;p&gt;This article accompanies a talk I gave about unit testing at Drupalcamp Ottawa on Octoboer 18, 2019, &lt;a href=&quot;https://alberto56.github.io/presentation-unit-test/#/&quot;&gt;here are the accompanying slides&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;before-we-start-testing&quot;&gt;Before we start testing&lt;/h2&gt;

&lt;p&gt;Unit tests are useless unless they are run on every change (commit) to a codebase through continuous integration (CI). And it’s excruciatingly painful to make CI work without some sort of platform-agnostic DevOps setup (we’ll use a Docker-based workflow), so before we even start testing, we’ll set up CI and Docker.&lt;/p&gt;

&lt;h2 id=&quot;docker-for-all-things&quot;&gt;Docker for all things&lt;/h2&gt;

&lt;p&gt;In the context of this article, we’ll define DevOps as a way to embed all dependencies within our code, meaning &lt;strong&gt;we want to limit the number of dependencies on our computer or CI server&lt;/strong&gt; to run our code. To do this, we will start by installing and starting &lt;a href=&quot;https://www.docker.com/products/docker-desktop&quot;&gt;Docker Desktop&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you’ve set it up, confirm you have Docker running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker -v
# Docker version 19.03.2, build 6a30dfc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point, we can be assured that any code we run through Docker will run on any machine which has Docker installed. In this article we’ll use mostly PHPUnit, so instead of installing and configuring PHPUnit on our computer &lt;em&gt;and&lt;/em&gt; our CI server &lt;em&gt;and&lt;/em&gt; our colleagues’ computers, we can simply make sure our computer and our CI server have Docker installed, and run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run --rm phpunit/phpunit --version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first time this is run on an environment, it should result in:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Unable to find image &apos;phpunit/phpunit:latest&apos; locally
latest: Pulling from phpunit/phpunit
Digest: sha256:bbbb143951f55fe93dbfed9adf130cae8623a1948f5a458e1aabbd175f7cb0b6
Status: Downloaded newer image for phpunit/phpunit:latest
PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On subsequent runs it will result in:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Installing PHPUnit can also be done through Composer. In this article we won’t use Composer because&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;that would require us to manage a specific version of PHP on each machine;&lt;/li&gt;
  &lt;li&gt;Composer does not work for programming languages other than PHP (say, for example, we want to unit test Javascript or Python).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2 id=&quot;host-your-code-on-github-or-bitbucket&quot;&gt;Host your code on Github or Bitbucket&lt;/h2&gt;

&lt;p&gt;We will avoid getting ahead of ourselves by learning and using Drupal’s unit test classes (which are based on PHPUnit) and testing infrastructure (we’ll do that below): we want to start by understanding how to unit test &lt;em&gt;any&lt;/em&gt; PHP code (Drupal or otherwise).&lt;/p&gt;

&lt;p&gt;To that end, we will need to host our code (or a mirror thereof) on non-Drupal infrastructure. Github and Bitbucket both integrate with &lt;a href=&quot;http://circleci.com&quot;&gt;CircleCI&lt;/a&gt;, a free, fast, and easy cloud continuous integration (CI) service with no vendor lock-in; we’ll use CircleCI later on in this article. With understanding of general unit testing principles under your belt, you can later move on to use framework-specific (including Drupal-specific) testing environments if you deem it necessary (for example if you are a contributor to core or to contrib modules which follow Drupal’s testing guidelines).&lt;/p&gt;

&lt;p&gt;To demonstrate the principles in this article, I have taken a random Drupal 8 module which, at the time of this writing, has no unit tests, &lt;a href=&quot;https://www.drupal.org/project/auto_entitylabel&quot;&gt;Automatic Entity Label&lt;/a&gt;. My selection is completely arbitrary, and I don’t use this module myself, and I’m not advocating you use it or not use it.&lt;/p&gt;

&lt;p&gt;So, as my first step, I have added &lt;a href=&quot;https://www.drupal.org/project/auto_entitylabel/releases/8.x-3.0-beta1&quot;&gt;v. 8.x-3.0-beta1&lt;/a&gt; of this module &lt;em&gt;as is&lt;/em&gt; to Github, and tagged it as “original”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/tree/original&quot;&gt;You can see the version I uploaded to Github, without tests, here&lt;/a&gt;. There are no unit tests – yet.&lt;/p&gt;

&lt;h2 id=&quot;start-continuous-integration&quot;&gt;Start continuous integration&lt;/h2&gt;

&lt;p&gt;Because, as we mentioned above, automated testing is all but useless without continuous integration (CI) to confirm your tests are passing, the next step is to set up CI. Attaching CircleCI to Github repos is straightforward. I started by adding a test that simply confirms that we can access PHPUnit on our CI environment.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/original...circle-ci&quot;&gt;Here is the changes I made to my code to add continuous integration&lt;/a&gt;. At this stage, this code only confirms that PHPUnit can be run via Docker, nothing else. If you want to follow along with your own codebase, you can add the same minor changes (in fact you are encouraged to do so). The change to the README.md document is a “Badge” which displays as green if tests pass, and red if they don’t, on the project’s home page. The rest is straightforward.&lt;/p&gt;

&lt;p&gt;Once your code is set up for CI integration, create an account and log on to &lt;a href=&quot;http://circleci.com&quot;&gt;CircleCI&lt;/a&gt; using your Github account (Bitbucket works also), select your project from your list of projects (“Set Up Project” button), and start building it (“Start Building” button); that’s it!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://circleci.com/gh/dcycle/unit-test-tutorial/1&quot;&gt;Here is my very first build for my version of Auto Entity Label&lt;/a&gt;. It is worth unfolding the “Tests” section and looking at the test results:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./scripts/ci.sh
Unable to find image &apos;phpunit/phpunit:latest&apos; locally
latest: Pulling from phpunit/phpunit
Digest: sha256:bbbb143951f55fe93dbfed9adf130cae8623a1948f5a458e1aabbd175f7cb0b6
Status: Downloaded newer image for phpunit/phpunit:latest
PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ll notice that you have output very similar to what you have on your own computer. That’s the magic of Docker: build once, run anywhere. Without it, Continuous Integration is like pulling teeth.&lt;/p&gt;

&lt;h2 id=&quot;setting-up-phpunit-to-actually-run-tests&quot;&gt;Setting up PHPUnit to actually run tests&lt;/h2&gt;

&lt;p&gt;Before we can test anything, PHPUnit needs to know where the tests reside, which tests to run, and how to autoload classes based on their namespace. Different frameworks, including Drupal, have recommendations on all this, but to get a good idea of how PHPUnit works, let’s start from scratch by creating four new files in our project (keep them empty for now):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;./phpunit.xml, at the root of our project, will define where are tests are located, and where our autoloader is located.&lt;/li&gt;
  &lt;li&gt;./phpunit-autoload.php, at the root of our project, is our autoloader; it tells PHPUnit that, for example, the namespace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drupal\auto_entitylabel\AutoEntityLabelManager&lt;/code&gt; corresponds to the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/AutoEntityLabelManager&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;./phpunit-bootstrap.php, we’ll leave empty for now, and look at it later on.&lt;/li&gt;
  &lt;li&gt;./tests/AutoEntityLabelManagerTest.php, which will contain a test for the AutoEntityLabelManager class.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;phpunitxml&quot;&gt;phpunit.xml&lt;/h3&gt;

&lt;p&gt;In this file, we’ll tell PHPUnit where to find our tests, and where the autoloader is. Different developers have their own preferences for what to put here, and Drupal has specific recommendations, but for now we’ll just use a simple file declaring that our tests are in ./tests (although they could be anywhere), and that the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phpunit-autoload.php&lt;/code&gt; (you could name it anything) should be loaded before each test is run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;phpunit bootstrap=&quot;phpunit-autoload.php&quot;&amp;gt;
  &amp;lt;testsuites&amp;gt;
    &amp;lt;testsuite name=&quot;myproject&quot;&amp;gt;
      &amp;lt;directory&amp;gt;./tests&amp;lt;/directory&amp;gt;
    &amp;lt;/testsuite&amp;gt;
  &amp;lt;/testsuites&amp;gt;
&amp;lt;/phpunit&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;phpunit-autoloadphp&quot;&gt;phpunit-autoload.php&lt;/h3&gt;

&lt;p&gt;In this file, we’ll tell PHPUnit how to find files based on namespaces. Different projects do this differently. For example, Drupal 7 has a custom Drupal-only way of autoloading classes; Drupal 8 uses the PSR-4 standard. In our example, we’re telling PHPUnit that any code which uses the class &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drupal\auto_entitylabel\Something&lt;/code&gt; will load the corresponding file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./src/Something.php&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php

/**
 * @file
 * PHPUnit class autoloader.
 *
 * PHPUnit knows nothing about Drupal, so provide PHPUnit with the bare
 * minimum it needs to know in order to find classes by namespace.
 *
 * Used by the PHPUnit test runner and referenced in ./phpunit.xml.
 */

spl_autoload_register(function ($class) {
  if (substr($class, 0, strlen(&apos;Drupal\\auto_entitylabel\\&apos;)) == &apos;Drupal\\auto_entitylabel\\&apos;) {
    $class2 = str_replace(&apos;Drupal\\auto_entitylabel\\&apos;, &apos;&apos;, $class);
    $path = &apos;src/&apos; . str_replace(&apos;\\&apos;, &apos;/&apos;, $class2) . &apos;.php&apos;;
    require_once $path;
  }
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;phpunit-bootstrapphp&quot;&gt;phpunit-bootstrap.php&lt;/h3&gt;

&lt;p&gt;(We’ll leave that one empty for now, but later on we’ll use it to put dummy versions of classes that Drupal code expects to find.)&lt;/p&gt;

&lt;h3 id=&quot;testsautoentitylabelmanagertestphp&quot;&gt;tests/AutoEntityLabelManagerTest.php&lt;/h3&gt;

&lt;p&gt;Here is our first test. Let’s start with a very simple unit test: once which tests a pure function with no externalities.&lt;/p&gt;

&lt;p&gt;Let’s take AutoEntityLabelManager::auto_entitylabel_entity_label_visible().&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/circle-ci/src/AutoEntityLabelManager.php#L359-L366&quot;&gt;Here it is context&lt;/a&gt;, and here is the actual code we want to test:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public static function auto_entitylabel_entity_label_visible($entity_type) {
  // @codingStandardsIgnoreEnd
  $hidden = [
    &apos;profile2&apos; =&amp;gt; TRUE,
  ];
  return empty($hidden[$entity_type]);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is actual code which exists in the Auto Entity Label project; I have never tried this function in a running Drupal instance, I’m not even sure why it’s there, &lt;em&gt;but I can still test it&lt;/em&gt;. I assume that if I call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AutoEntityLabelManager::auto_entitylabel_entity_label_visible(&apos;whatever&apos;)&lt;/code&gt;, I should get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TRUE&lt;/code&gt; as a response. This is what I will test for in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./tests/AutoEntityLabelManagerTest.php&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php

namespace Drupal\auto_entitylabel\Tests;

use Drupal\auto_entitylabel\AutoEntityLabelManager;
use PHPUnit\Framework\TestCase;

/**
 * Test AutoEntityLabelManager.
 *
 * @group myproject
 */
class AutoEntityLabelManagerTest extends TestCase {

  /**
   * Test for auto_entitylabel_entity_label_visible().
   *
   * @cover ::auto_entitylabel_entity_label_visible
   */
  public function testAuto_entitylabel_entity_label_visible() {
    $this-&amp;gt;assertTrue(AutoEntityLabelManager::auto_entitylabel_entity_label_visible(&apos;whatever&apos;) === TRUE, &apos;Label &quot;whatever&quot; is visible.&apos;);
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For test methods to be called by PHPUnit, they need to start with a lowercase &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(If you have looked at other Drupal unit testing tutorials, you might have noticed that Drupal unit tests are based not on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PHPUnit\Framework\TestCase&lt;/code&gt; but on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drupal\Tests\UnitTestCase&lt;/code&gt;. The latter provides some useful, but not critical, helper code. In our case, using PHPUnit directly without Drupal means we don’t depend on Drupal to run our code; and we can better understand the intricacies of PHPUnit.)&lt;/p&gt;

&lt;h3 id=&quot;scriptscish&quot;&gt;scripts/ci.sh&lt;/h3&gt;

&lt;p&gt;Finally we’ll need to tweak ./scripts/ci.sh a bit:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run --rm -v &quot;$(pwd)&quot;:/app phpunit/phpunit \
  --group myproject
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-v &quot;$(pwd)&quot;:/app&lt;/code&gt; shares our code on our host computer or server with a directory called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/app&lt;/code&gt; on the PHPUnit Docker container, so PHPUnit actually has access to our code. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--group myproject&lt;/code&gt; runs all tests in the “myproject” group (recall that in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tests/AutoEntityLabelManagerTest.php&lt;/code&gt;, we have added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@group myproject&lt;/code&gt; to the class comment).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/circle-ci...first-problem&quot;&gt;Here are the changes we made to our code&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;running-our-first-test-and-running-into-our-first-problem&quot;&gt;Running our first test… and running into our first problem&lt;/h2&gt;

&lt;p&gt;With all those changes in place, if you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/ci.sh&lt;/code&gt;, you should have this output:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ./scripts/ci.sh
PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…and this Fatal error…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PHP Fatal error:  Trait &apos;Drupal\Core\StringTranslation\StringTranslationTrait&apos; not found in /app/src/AutoEntityLabelManager.php on line 16
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So what’s happening here? It turns out &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AutoEntityLabelManager&lt;/code&gt; &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/circle-ci/src/AutoEntityLabelManager.php#L16&quot;&gt;uses something called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringTranslationTrait&lt;/code&gt;&lt;/a&gt;. A PHP trait is a code sharing pattern. It’s a fascinating topic and super useful to write testable code (we’ll get to it later); but right now we don’t need it and don’t really care about it, it’s just getting in the way of our test. We somehow need to tell PHPUnit that &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/first-problem/src/AutoEntityLabelManager.php#L5&quot;&gt;Drupal\Core\StringTranslation\StringTranslationTrait&lt;/a&gt; needs to exist, &lt;em&gt;but we don’t really care – right now – what it does&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That’s where our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;phpunit-bootstrap.php&lt;/code&gt; file comes in. In it, we can define &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drupal\Core\StringTranslation\StringTranslationTrait&lt;/code&gt; so that PHP will not complain that it does not exit.&lt;/p&gt;

&lt;p&gt;In phpunit-autoload.php, require phpunit-bootsrap.php:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;require_once &apos;phpunit-bootstrap.php&apos;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And in phpunit-bootsrap.php, define a dummy version of Drupal\Core\StringTranslation\StringTranslationTrait:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php

/**
 * @file
 *
 * PHPUnit knows nothing about Drupal. Declare required classes here.
 */

namespace Drupal\Core\StringTranslation {
  trait StringTranslationTrait {}
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/first-problem...first-running-test&quot;&gt;Here is the diff in our repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;running-our-first-passing-test&quot;&gt;Running our first passing test!&lt;/h2&gt;

&lt;p&gt;This is a big day for you, it’s &lt;em&gt;the day of your first passing test&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ./scripts/ci.sh
PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.

.                                                                   1 / 1 (100%)

Time: 124 ms, Memory: 4.00MB

OK (1 test, 1 assertion)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because of the magic of Docker, the same output can be found on &lt;a href=&quot;https://circleci.com/gh/dcycle/unit-test-tutorial/3&quot;&gt;our CI infrastructure’s equivalent passing test&lt;/a&gt; (by unfolding the “Tests” section) once we push our code to Github.&lt;/p&gt;

&lt;h2 id=&quot;introducing-test-providers&quot;&gt;Introducing test &lt;em&gt;providers&lt;/em&gt;&lt;/h2&gt;

&lt;p&gt;OK, we’re getting into the jargon of PHPUnit now. To introduce the concept of test providers, consider this: almost every time we run a test, we’d like to bombard our &lt;em&gt;unit&lt;/em&gt; (our PHP method) with a variety of inputs and expected outputs, and confirm our unit always works as expected.&lt;/p&gt;

&lt;p&gt;The basic testing code is always the same, but the inputs and expected outputs change.&lt;/p&gt;

&lt;p&gt;Consider our existing test:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Test for auto_entitylabel_entity_label_visible().
 *
 * @cover ::auto_entitylabel_entity_label_visible
 */
public function testAuto_entitylabel_entity_label_visible() {
  $this-&amp;gt;assertTrue(AutoEntityLabelManager::auto_entitylabel_entity_label_visible(&apos;whatever&apos;) === TRUE, &apos;Label &quot;whatever&quot; is visible.&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Maybe calling our method with “whatever” should yield TRUE, but we might also want to test other inputs to make sure we cover every possible usecase for the method. In our case, looking at &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/circle-ci/src/AutoEntityLabelManager.php#L359-L366&quot;&gt;the method&lt;/a&gt;, we can reasonably surmise that calling it with “profile2” should yield FALSE. Again, I’m not sure why this is; in the context of this tutorial, all I want to do is to make sure the method works as expected.&lt;/p&gt;

&lt;p&gt;So the answer here is to serarate the testing code from the inputs and expected outputs. That’s where the &lt;em&gt;provider&lt;/em&gt; comes in. We will add arguments to the test code, and define a separate function which calls our test code with different arguments. The end results looks like this (I also like to print_r() the expected and actual output in case they differ, but this is not required):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Test for auto_entitylabel_entity_label_visible().
 *
 * @param string $message
 *   The test message.
 * @param string $input
 *   Input string.
 * @param bool $expected
 *   Expected output.
 *
 * @cover ::auto_entitylabel_entity_label_visible
 * @dataProvider providerAuto_entitylabel_entity_label_visible
 */
public function testAuto_entitylabel_entity_label_visible(string $message, string $input, bool $expected) {
  $output = AutoEntityLabelManager::auto_entitylabel_entity_label_visible($input);

  if ($output != $expected) {
    print_r([
      &apos;output&apos; =&amp;gt; $output,
      &apos;expected&apos; =&amp;gt; $expected,
    ]);
  }

  $this-&amp;gt;assertTrue($output === $expected, $message);
}

/**
 * Provider for testAuto_entitylabel_entity_label_visible().
 */
public function providerAuto_entitylabel_entity_label_visible() {
  return [
    [
      &apos;message&apos; =&amp;gt; &apos;Label &quot;whatever&quot; is visible&apos;,
      &apos;input&apos; =&amp;gt; &apos;whatever&apos;,
      &apos;expected&apos; =&amp;gt; TRUE,
    ],
    [
      &apos;message&apos; =&amp;gt; &apos;Label &quot;profile2&quot; is invisible&apos;,
      &apos;input&apos; =&amp;gt; &apos;profile2&apos;,
      &apos;expected&apos; =&amp;gt; FALSE,
    ],
    [
      &apos;message&apos; =&amp;gt; &apos;Empty label is visible&apos;,
      &apos;input&apos; =&amp;gt; &apos;&apos;,
      &apos;expected&apos; =&amp;gt; TRUE,
    ],
  ];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/first-running-test...provider&quot;&gt;Here is the diff in GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At this point, we have one test method being called with three different sets of data, so the same test method is being run three times; running the test now shows three dots:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ./scripts/ci.sh
PHPUnit 6.5.13 by Sebastian Bergmann, Julien Breux (Docker) and contributors.

...                                                                 3 / 3 (100%)

Time: 232 ms, Memory: 4.00MB

OK (3 tests, 3 assertions)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;breaking-down-monster-functions&quot;&gt;Breaking down monster functions&lt;/h2&gt;

&lt;p&gt;It must be human nature, but over time, during development, functions tend to get longer and longer, and more and more complex. Functions longer than a few lines tend to be hard to test, because of the sheer number of possible execution paths, especially if there are several levels of control statements.&lt;/p&gt;

&lt;p&gt;Let’s take, as an example, &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/provider/auto_entitylabel.module#L69-L114&quot;&gt;auto_entitylabel_prepare_entityform()&lt;/a&gt;. With its multiple switch and if statements, it has a &lt;a href=&quot;https://pdepend.org/documentation/software-metrics/cyclomatic-complexity.html&quot;&gt;cyclomatic complexity&lt;/a&gt; of 7, the highest in this codebase, according to the static analysis tool &lt;a href=&quot;https://pdepend.org/&quot;&gt;Pdepend&lt;/a&gt;. If you’re curious about finding your cyclomatic complexity, you can use the magic of Docker, run the following, and take a look at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./php_code_quality/pdepend_output.xml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir -p php_code_quality &amp;amp;&amp;amp; docker run -it --rm -v &quot;$PWD&quot;:/app -w /app adamculp/php-code-quality:latest php /usr/local/lib/php-code-quality/vendor/bin/pdepend --suffix=&apos;php,module&apos; --summary-xml=&apos;./php_code_quality/pdepend_output.xml&apos; .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;See &lt;a href=&quot;https://hub.docker.com/r/adamculp/php-code-quality&quot;&gt;adamculp/php-code-quality&lt;/a&gt; for more details. But I digress…&lt;/p&gt;

&lt;p&gt;Testing this completely would require close to 2 to the power 7 test providers, so the easiest is to break it down into smaller functions with a lower cyclomatic complexity (that is, fewer control statements). We’ll get to that in a minute, but first…&lt;/p&gt;

&lt;h2 id=&quot;procedural-code-is-not-testable-use-class-methods&quot;&gt;Procedural code is not testable, use class methods&lt;/h2&gt;

&lt;p&gt;For all but pure functions, procedural code like our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto_entitylabel_prepare_entityform()&lt;/code&gt;, as well as private and static methods, are untestable with mock objects (which we’ll get those later). Therefore, any code you’d like to test should exist within a class. For our purposes, we’ll put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;auto_entitylabel_prepare_entityform()&lt;/code&gt; within a &lt;a href=&quot;https://en.wikipedia.org/wiki/Singleton_pattern&quot;&gt;Singleton&lt;/a&gt; class, &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/provider...procedural-to-class&quot;&gt;like this&lt;/a&gt;, and name it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prepareEntityForm()&lt;/code&gt;. (You don’t need to use a Singleton; you can use a Drupal service or whatever you want, as long as everything you want to test is a non-static class method.)&lt;/p&gt;

&lt;h2 id=&quot;our-second-test&quot;&gt;Our second test&lt;/h2&gt;

&lt;p&gt;So we put our procedural code in a class. But the problem remains: it’s too complex to fully cover with unit tests, so as a next step I recommend surgically removing only those parts of the method we want to test, and putting them in a separate method. Let’s focus on &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/procedural-to-class/src/AutoEntityLabelSingleton.php#L52-L54&quot;&gt;these lines of code&lt;/a&gt;, which can lead to &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/procedural-to-class...split-monster&quot;&gt;this change in our code&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;object-and-method-mocking-and-stubs&quot;&gt;Object and method mocking, and stubs&lt;/h2&gt;

&lt;p&gt;Let’s consider a scenario where we want to add some tests to &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/split-monster/src/Plugin/Validation/EntityLabelNotNullConstraintValidator.php#L46-L61&quot;&gt;EntityLabelNotNullConstraintValidator::validate()&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s start by splitting the validate method into smaller parts, &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/split-monster...split-validate-method&quot;&gt;like this&lt;/a&gt;. We will now focus on testing a more manageable method with a lower cyclomatic complexity:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Manage typed data if it is valid.
 *
 * @return bool
 *   FALSE if the parent class validation should be called.
 */
public function manageTypedData() : bool {
  $typed_data = $this-&amp;gt;getTypedData();
  if ($typed_data instanceof FieldItemList &amp;amp;&amp;amp; $typed_data-&amp;gt;isEmpty()) {
    return $this-&amp;gt;manageValidTypedData($typed_data);
  }
  return FALSE;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Recall that in unit testing, &lt;strong&gt;we are only testing single units of code&lt;/strong&gt;. In this case, the unit of code we are testing is manageTypedData(), above.&lt;/p&gt;

&lt;p&gt;In order to test `manageTypedData() &lt;strong&gt;and nothing else&lt;/strong&gt;, conceptually, &lt;strong&gt;we need to assume that getTypedData() and manageValidTypedData() are doing their jobs, we will not call them, but replace them with stub methods within a mock object.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We want to avoid calling getTypedData() and manageValidTypedData() because that would interfere with our testing of manageTypedData() – we need to &lt;em&gt;mock&lt;/em&gt; getTypedData() and manageValidTypedData().&lt;/p&gt;

&lt;p&gt;When we test &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manageTypedData()&lt;/code&gt; in this way, we need to replace the real &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getTypedData()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manageValidTypedData()&lt;/code&gt; with mock methods and make them return whatever we want.&lt;/p&gt;

&lt;p&gt;PHPUnit achieves this by making a copy of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EntityLabelNotNullConstraintValidator&lt;/code&gt; class, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getTypedData()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manageValidTypedData()&lt;/code&gt; are replaced with our own methods which return what we want. So in the context of our test, we do not instantiate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EntityLabelNotNullConstraintValidator&lt;/code&gt;, but rather, a mock version of that class in which we replace certain methods. Here is how to instantiate that class:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$object = $this-&amp;gt;getMockBuilder(EntityLabelNotNullConstraintValidator::class)
  -&amp;gt;setMethods([
    &apos;getTypedData&apos;,
    &apos;manageValidTypedData&apos;,
  ])
  -&amp;gt;disableOriginalConstructor()
  -&amp;gt;getMock();
// We don&apos;t care how getTypedData() figures out what to return to
// manageTypedData, but we do want to see how our function will react
// to a variety of possibilities.
$object-&amp;gt;method(&apos;getTypedData&apos;)
  -&amp;gt;willReturn($input);
// We will assume manageValidTypedData() is doing its job; that&apos;s not
// what were are testing here. For our test, it will always return TRUE.
$object-&amp;gt;method(&apos;manageValidTypedData&apos;)
  -&amp;gt;willReturn(TRUE);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, our new object behaves exactly as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EntityLabelNotNullConstraintValidator&lt;/code&gt;, except that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getTypedData()&lt;/code&gt; returns $input (which we’ll define in a &lt;em&gt;provider&lt;/em&gt;); and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manageValidTypedData()&lt;/code&gt; always returns TRUE.&lt;/p&gt;

&lt;p&gt;Keep in mind that private methods cannot be mocked, so for that reason I generally avoid using them; use protected methods instead.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/split-validate-method...manageTypedData-test1&quot;&gt;Here is our initial test for this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our provider, at this point, only makes sure that if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getTypedData()&lt;/code&gt; returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new \stdClass()&lt;/code&gt; &lt;strong&gt;which is not an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;instanceof&lt;/code&gt; FieldItemList&lt;/strong&gt;, then the method we’re testing will return FALSE.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/manageTypedData-test1...anon-class&quot;&gt;Here is how we could extend our provider&lt;/a&gt; to make sure our method reacts correctly if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getTypedData()&lt;/code&gt; returns a &lt;strong&gt;FieldItemList&lt;/strong&gt; whose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isEmpty()&lt;/code&gt; method returns TRUE, and FALSE.&lt;/p&gt;

&lt;h2 id=&quot;testing-protecting-methods&quot;&gt;Testing protecting methods&lt;/h2&gt;

&lt;p&gt;Let’s say we want to (partially) test the protected &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/anon-class/src/AutoEntityLabelManager.php#L281-L295&quot;&gt;AutoEntityLabelManager::getConfig()&lt;/a&gt;, we need to introduce a new trick.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/anon-class...fail-protected&quot;&gt;Start by taking a look at our test code which fails&lt;/a&gt;. If you try to run this, you will get:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;There was 1 error:

1) Drupal\auto_entitylabel\Tests\AutoEntityLabelManagerTest::testGetConfig
Error: Cannot access protected property Mock_AutoEntityLabelManager_0f5704cf::$config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So we want to test a protected method (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getConfig()&lt;/code&gt;), and, in order to test it, we need to modify a protected property (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$config&lt;/code&gt;). These two will result in “Cannot access”-type failures.&lt;/p&gt;

&lt;p&gt;The solution is to use a trick known as class reflection; it’s a bit opaque, but it does allow us to access protected properties and methods.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/fail-protected...reflection&quot;&gt;Take a look at some changes which result in a working version of our test&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Copy-pasting is perhaps your best fiend here, because this concept kind of plays with your mind. But basically, a ReflectionClass allows us to retrieve properties and methods &lt;em&gt;as objects&lt;/em&gt;, then set their visibility using methods of those objects, then set their values or call them using their own methods… As I said, copy-pasting is good, sometimes.&lt;/p&gt;

&lt;h2 id=&quot;a-note-about-testing-abstract-classes&quot;&gt;A note about testing abstract classes&lt;/h2&gt;

&lt;p&gt;There are no abstract classes in Auto Entity Label, but if you want to test an abstract class, here is how to create a mock object:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$object = $this-&amp;gt;getMockBuilder(MyAbstractClass::class)
  -&amp;gt;setMethods(NULL)
  -&amp;gt;disableOriginalConstructor()
  -&amp;gt;getMockForAbstractClass();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;using-traits&quot;&gt;Using traits&lt;/h2&gt;

&lt;p&gt;Consider the following scenario: a bunch of your code uses the legacy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupal_set_message()&lt;/code&gt; method. You might have something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class a extends some_class {
  public function a() {
    ...
    drupal_set_message(&apos;hello&apos;);
    ...
  }
}

class b extends some_other_class {
  public function b() {
    ...
    drupal_set_message(&apos;world&apos;);
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your tests will complain if you try to call, or mock &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupal_set_message()&lt;/code&gt; when unit-testing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a::a()&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b::b()&lt;/code&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;, because &lt;/code&gt;drupal_set_message()` is procedural and you can’t do much with it (thankfully there is fewer and fewer procedural code in Drupal modules, but you’ll still find a lot of it).&lt;/p&gt;

&lt;p&gt;So in order to make &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupal_set_message()&lt;/code&gt; mockable, you might want to something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class a extends some_class {
  protected method drupalSetMessage($x) {
    drupal_set_message($x);
  }
  public function a() {
    ...
    $this-&amp;gt;drupalSetMessage(&apos;hello&apos;);
    ...
  }
}

class b extends some_other_class {
  protected method drupalSetMessage($x) {
    drupal_set_message($x);
  }
  public function b() {
    ...
    $this-&amp;gt;drupalSetMessage(&apos;world&apos;);
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, however, we’re in code duplication territory, which is not cool (well, not much of what we’re doing is cool, not in the traditional sense anyway). We can’t define a base class which has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupalSetMessage()&lt;/code&gt; as a method because PHP doesn’t (and probably shouldn’t) support multiple inheritance. That’s where traits come in, it’s a technique for code reuse which is exactly adapted to this situation:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;trait commonMethodsTrait {
  protected method drupalSetMessage($x) {
    drupal_set_message($x);
  }
}

class a extends some_class {
  use commonMethodsTrait;

  public function a() {
    ...
    $this-&amp;gt;drupalSetMessage(&apos;hello&apos;);
    ...
  }
}

class b extends some_other_class {
  use commonMethodsTrait;

  public function b() {
    ...
    $this-&amp;gt;drupalSetMessage(&apos;world&apos;);
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Drupal uses this a lot: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t()&lt;/code&gt; method is peppered in most of core and contrib; earlier in this article we ran into &lt;a href=&quot;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21StringTranslation%21StringTranslationTrait.php/trait/StringTranslationTrait/8.2.x&quot;&gt;StringTranslationTrait&lt;/a&gt;; that allows developers to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$this-&amp;gt;t()&lt;/code&gt; instead of the legacy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t()&lt;/code&gt;, therefore making it mockable when testing methods which use it. The great thing about this approach is that we do not even need Drupal’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringTranslationTrait&lt;/code&gt; when running our tests, we can mock t() even if a &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/blob/reflection/phpunit-bootstrap.php#L10&quot;&gt;dummy version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringTranslationTrait&lt;/code&gt;&lt;/a&gt; is used.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/reflection...test-t&quot;&gt;Check out this test for an example&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-about-javascript-python-and-other-languages&quot;&gt;What about Javascript, Python and other languages?&lt;/h2&gt;

&lt;p&gt;PHP has PHPUnit; other languages also have their test suites, and they, too, can run within Docker. Javascript has &lt;a href=&quot;https://github.com/avajs/ava&quot;&gt;AVA&lt;/a&gt;; &lt;a href=&quot;https://docs.python.org/2/library/unittest.html&quot;&gt;Python has unittest&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All unit test frameworks support mocking.&lt;/p&gt;

&lt;p&gt;Let’s look a bit more closely at &lt;a href=&quot;https://github.com/avajs/ava&quot;&gt;AVA&lt;/a&gt;, but we do not want to install and maintain it on all our developers’ machines, and on our CI server, so we’ll use a &lt;a href=&quot;https://github.com/dcycle/docker-ava&quot;&gt;Dockerized version of AVA&lt;/a&gt;. We can download that project and, specifically, run tests against  &lt;a href=&quot;https://github.com/dcycle/docker-ava/tree/master/example03&quot;&gt;example 3&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone git@github.com:dcycle/docker-ava.git
docker run -v $(pwd)/example03/test:/app/code \
  -v $(pwd)/example03/code:/mycode dcycle/ava
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The result here, again due to the magic of Docker, should be:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 passed
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So what’s going on here? We have &lt;a href=&quot;https://github.com/dcycle/docker-ava/blob/master/example03/code/dangerlevel.js&quot;&gt;some sample Javascript&lt;/a&gt; code which has a function we’d like to test:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;module.exports = {
  dangerlevel: function(){
    return this.tsunamidangerlevel() * 4 + this.volcanodangerlevel() * 10;
  },

  tsunamidangerlevel: function(num){
    // Call some external API.
    return this_will_fail_during_testing();
    // During tests, we want to ignore this function.
  },

  volcanodangerlevel: function(num){
    // Call some external API.
    return this_will_fail_during_testing();
    // During tests, we want to ignore this function.
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this specific case we’d like to mock &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsunamidangerlevel()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;volcanodangerlevel()&lt;/code&gt; during unit testing: we don’t care that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;this_will_fail_during_testing()&lt;/code&gt; is unknown to our test code. Our test &lt;a href=&quot;https://github.com/dcycle/docker-ava/blob/master/example03/test/test.js&quot;&gt;could look something like this&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import test from &apos;ava&apos;
import sinon from &apos;sinon&apos;

var my = require(&apos;/mycode/dangerlevel.js&apos;);

test(&apos;Danger level is correct&apos;, t =&amp;gt; {
  sinon.stub(my, &apos;tsunamidangerlevel&apos;).returns(1);
  sinon.stub(my, &apos;volcanodangerlevel&apos;).returns(2);

  t.true(my.dangerlevel() == 24);
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What we’re saying here is that if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tsunamidangerlevel()&lt;/code&gt; returns 1 and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;volcanodangerlevel()&lt;/code&gt; returns 2, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dangerlevel()&lt;/code&gt; should return 24.&lt;/p&gt;

&lt;h2 id=&quot;the-drupal-testbot&quot;&gt;The Drupal testbot&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Edit (December 10, 2019): &lt;a href=&quot;https://github.com/dcycle/drupal-tester/issues/2&quot;&gt;until this issue is fixed&lt;/a&gt; I recommend using the CircleCI technique and not testing on the Drupal infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drupal has its own Continuous Integration infrastructure, or testbot. It’s a bit more involving to reproduce its results locally; still, you might want to use if you are developing a Drupal module; and indeed you’ll have to use if it you are submitting patches to core.&lt;/p&gt;

&lt;p&gt;In fact, it is possible to tweak our code a bit to allow it to run on the Drupal testbot &lt;em&gt;and&lt;/em&gt; CircleCI.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/test-t...drupaltestbot&quot;&gt;Here are some changes to our code which allow exactly that&lt;/a&gt;. Let’s go over the changes required:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Tests need to be in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./tests/src/Unit&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;The @group name should be unique to your project (you can use your project’s machine name);&lt;/li&gt;
  &lt;li&gt;The tests should have the namespace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drupal\Tests\my_project_machine_name\Unit&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drupal\Tests\my_project_machine_name\Unit\Sub\Folder&lt;/code&gt; (for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drupal\Tests\my_project_machine_name\Unit\Plugin\Validation&lt;/code&gt;);&lt;/li&gt;
  &lt;li&gt;The unit tests have access to Drupal code. This is actually quite annoying, for example, we can &lt;a href=&quot;https://github.com/dcycle/unit-test-tutorial/compare/test-t...drupaltestbot#diff-5a0a42c64de5d295f959f87167210018R62-L87&quot;&gt;no longer just create an anonymous class for FieldItemList&lt;/a&gt; but rather, we need to create a mock object using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;disableOriginalConstructor()&lt;/code&gt;; this is because, the unit test code being aware of Drupal, it knows that FieldItemList requires parameters to its constructor; and therefore it complains when we don’t have any (in the case of an anonymous object).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make sure this works, I created a project (it has to be a full project, as far as I can tell, can’t be a sandbox project, or at least I didn’t figure out to do this with a sandbox project) at &lt;a href=&quot;https://www.drupal.org/project/unit_test_tutorial&quot;&gt;Unit Test Tutorial&lt;/a&gt;. I then activated automated testing under the &lt;a href=&quot;https://www.drupal.org/node/3088433/qa&quot;&gt;Automated testing tab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The results can be seen on &lt;a href=&quot;https://dispatcher.drupalci.org/job/drupal_contrib/60976/console&quot;&gt;the Drupal testbot&lt;/a&gt;. Look for these lines specifically:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;20:32:38 Drupal\Tests\auto_entitylabel\Unit\AutoEntityLabelSingletonT   2 passes
20:32:38 Drupal\Tests\auto_entitylabel\Unit\AutoEntityLabelManagerTes   4 passes
20:32:38 Drupal\Tests\auto_entitylabel\Unit\Plugin\Validation\EntityL   1 passes
20:32:38 Drupal\Tests\auto_entitylabel\Unit\Form\AutoEntityLabelFormT   1 passes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My main annoyance with using the Drupal testbot is that it’s hard to test locally; you need to have access to a Drupal instance with PHPUnit installed as a dev dependency, and a database. To remedy this, the &lt;a href=&quot;http://github.com/dcycle/drupal-tester/blob/master/README.md&quot;&gt;Drupal Tester&lt;/a&gt; Docker project can be used to run Drupal-like tests locally, here is how:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/dcycle/drupal-tester.git
cd drupal-tester/
mkdir -p modules
cd modules
git clone --branch 8.x-1.x https://git.drupalcode.org/project/unit_test_tutorial.git
cd ..
./scripts/test.sh &quot;--verbose --suppress-deprecations unit_test_tutorial&quot;
docker-compose down -v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will give you more or less the same results as the Drupal testbot:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Drupal\Tests\auto_entitylabel\Unit\AutoEntityLabelManagerTes   4 passes
Drupal\Tests\auto_entitylabel\Unit\AutoEntityLabelSingletonT   2 passes
Drupal\Tests\auto_entitylabel\Unit\Form\AutoEntityLabelFormT   1 passes
Drupal\Tests\auto_entitylabel\Unit\Plugin\Validation\EntityL   1 passes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h2&gt;

&lt;p&gt;Our promise, from the title of this article, is “Start unit testing your PHP code today”. Hopefully the tricks herein will allow you to do just that. My advice to you, dear testers, is to &lt;strong&gt;start by using Docker locally&lt;/strong&gt;, &lt;strong&gt;then to make sure you have Continuous Integration set up (on Drupal testbot or CircleCI, or, as in our example, both)&lt;/strong&gt;, and &lt;strong&gt;&lt;em&gt;only then&lt;/em&gt; start testing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
        
          <description>&lt;p&gt;Unit tests are the fastest, most reliable kinds of tests: they confirm that the smallest &lt;em&gt;units&lt;/em&gt; of your code, i.e. class methods, work as expected.&lt;/p&gt;

</description>
        
        <pubDate>Wed, 16 Oct 2019 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2019-10-16/unit-testing/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2019-10-16/unit-testing/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>An approach to automating Drupal accessibility tests</title>
        <description>&lt;p&gt;Accessibility tests can be automated to a degree, but not completely; to succeed at accessibility, it needs to be a mindset shared by developers, UX and front-end folks, business people and other stakeholders. In this article, we will attempt to run tests and produce meaningful metrics which can help teams who are already committed to produce more accessible websites.&lt;/p&gt;

&lt;h2 id=&quot;premise&quot;&gt;Premise&lt;/h2&gt;

&lt;p&gt;Say your team is developing a Drupal 8 site and you have decided that you want to reduce its accessibility issues by 50% over the course of six months.&lt;/p&gt;

&lt;p&gt;In this article, we will look at a subset of accessibility issues which can be automatically checked – color contrast, placement of tags and HTML attributes, for example. Furthermore, we will only test the code itself with some dummy data, not actual live data or environment. Therefore, if you use the approach outlined in this article, it is best to do so within a global approach which includes stakeholder training; and automated and manual monitoring of live environments, all of which are outside the scope of this article.&lt;/p&gt;

&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;/h2&gt;

&lt;p&gt;Your team is probably perpetually “too busy” to fix accessibility issues; and therefore too busy to read and process reports with dozens, perhaps hundreds, of accessibility problems on thousands of pages.&lt;/p&gt;

&lt;p&gt;Instead of expecting teams to process accessibility reports, we will use a &lt;strong&gt;threshold&lt;/strong&gt; approach:&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;determine a standard&lt;/strong&gt; towards which you’d like to work, for example WCAG 2.0 AA is more stringent than WCAG 2.0 A (but if you’re working on a U.S. Government website, WCAG 2 AA is mandated by the Americans with Disabilities Act). Be realistic as to the level of effort your team is ready to deploy.&lt;/p&gt;

&lt;p&gt;Next (we’ll see how to do this later), &lt;strong&gt;figure out which pages&lt;/strong&gt; you’d like to test against: perhaps one article, one event page, the home page, perhaps an internal page for logged in users.&lt;/p&gt;

&lt;p&gt;In this article, to keep things simple, we’ll test for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the home page;&lt;/li&gt;
  &lt;li&gt;an public-facing internal page, /node/1;&lt;/li&gt;
  &lt;li&gt;the /user page for users who are logged in;&lt;/li&gt;
  &lt;li&gt;the node editing form at /node/1/edit (for users who are logged in, obviously).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running accessibility checks on each of the above pages, we will end up with our &lt;strong&gt;baseline threshold&lt;/strong&gt;, the current number of errors, for example this might be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;6 for the home page&lt;/li&gt;
  &lt;li&gt;6 for /node/1&lt;/li&gt;
  &lt;li&gt;10 for /user&lt;/li&gt;
  &lt;li&gt;10 for /node/1/edit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will then make our tests fail if there are more errors on a given page than we allow for. The test should pass at first, and this approach meets several objectives:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, have an idea of the state of your site: are there 10 accessibility errors on the home page, or 1000?&lt;/li&gt;
  &lt;li&gt;Fail immediately if a developer opens a pull request where the number of accessibility errors increases past the threshold for any given page. For example, if a widget is added to the /user page which makes the number of accessibility errors jump to 12 (in this example), we should see a failure in our continuous integration infrastructure because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;12 &amp;gt;= 10&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Provide your team with the tools to &lt;strong&gt;reduce the threshold over time&lt;/strong&gt;. Concretely, a discussion with all stakeholders can be had once the initial metrics are in place; a decision might be made that we want to reduce thresholds for each page by 50% within 6 months. This allows your technical team to justify the prioritization of time spent on accessibility fixes vs. other tasks seen by able-bodied stakeholders as having a more direct business value.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;principles&quot;&gt;Principles&lt;/h2&gt;

&lt;h3 id=&quot;principle-1-docker-for-everything&quot;&gt;Principle #1: Docker for everything&lt;/h3&gt;

&lt;p&gt;Because we want to run tests on a continuous integration server, we want to avoid dependencies. Specifically, we want a system which does not require us to install specific versions of MySQL, PHP, headless browsers, accessibility checkers, etc. All our dependencies will be embedded into our project using Docker and Docker Compose. That way, all you need to install in order to run your project and test for accessibility (and indeed other tests) is Docker, which in most cases includes Docker Compose.&lt;/p&gt;

&lt;h3 id=&quot;principle-2-a-starter-database&quot;&gt;Principle #2: A starter database&lt;/h3&gt;

&lt;p&gt;In our continous integration setup, will will be testing our code on every commit. Although it can be useful to test, or monitor, a remote environment such as the live or staging site, &lt;em&gt;this is not what this article is about&lt;/em&gt;. This means we need some way to include dummy data into our codebase. We will do this by adding dummy data into a “starter database” committed to version control. (Be careful not to rely on this starter database to move configuration to the production site – use configuration management for that – we only want to store dummy &lt;em&gt;data&lt;/em&gt; in our starter database; all configuration should be in code.) In our example, our starter database will contain node/1 with some realistic dummy data. This is required because as part of our test we want to run accessibility checks agains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/node/1&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/node/1/edit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A good practice during development would be that for new data types, say a new content type “sandwich”, a new version of the starter database be created with, say, node/2 of type “sandwich”, with realistic data in all its fields. This will allow us to add an accessibility test for /node/2, and /node/2/edit if we wish.&lt;/p&gt;

&lt;h2 id=&quot;tools&quot;&gt;Tools&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don’t forget, as per principle #1, above, you will never need to install anything other than Docker on your computer or CI server, so don’t attempt to install these tools locally, they will run on Docker containers which will be built automatically for you.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Pa11y&lt;/strong&gt;: There are dozens of tools to check for accessibility; in this article we’ve settled on Pa11y because it provides clear error reports; and allows the concept of a threshold above which the script fails.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Chromium&lt;/strong&gt;: In order to check a page for accessibility issues without actually having a browser open, a so-called headless browser is needed. Chromium is a fully functional browser which works on the command line and can be scripted. This works under the hood and you will have no need to install it or interact with it directly, it’s just good to know it’s there.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Puppeteer&lt;/strong&gt;: most accessibility tools, including Pa11y, are good at testing one page. Say, if you point Pa11y to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/node/1&lt;/code&gt; or the home page, it will generate nice reports with thresholds. However if you point Pa11y to /user or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/node/1/edit&lt;/code&gt; it will see those pages anonymously, which is not what we want to test. This is where Puppeteer, a browser scripting tool, comes into play. We will use Puppeteer later on to log into our site and save the markup of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/user&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/node/1/edit&lt;/code&gt; as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dom-captures/user.html&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dom-captures/node-1-edit.html&lt;/code&gt;, respectively, which will then allow Pa11y to access and test those paths anonymously.&lt;/li&gt;
  &lt;li&gt;And of course, &lt;strong&gt;Drupal 8&lt;/strong&gt;, although you could apply the technique in this article to any web technology, because our accessibility checks are run against the web pages just like an end user would see them; there is no interaction with Drupal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;

&lt;p&gt;To follow along, you can install and start &lt;a href=&quot;https://www.docker.com/products/docker-desktop&quot;&gt;Docker Desktop&lt;/a&gt; and &lt;a href=&quot;http://github.com/dcycle/starterkit-drupal8site&quot;&gt;download the Dcycle Drupal 8 starterkit&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/dcycle/starterkit-drupal8site.git
cd starterkit-drupal8site
./scripts/deploy.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You are also welcome to fork the project and link it to a free &lt;a href=&quot;http://circleci.com&quot;&gt;CircleCI&lt;/a&gt; account, in which case continuous integration tests should start running immediately on every commit.&lt;/p&gt;

&lt;p&gt;A few minutes after running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/deploy.sh&lt;/code&gt;, you should see a login link to a full Drupal installation on a random local port (for example http://0.0.0.0:32769) with some dummy data (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/node/1&lt;/code&gt;). Deploying this site locally or on a CI server such as Circle CI is a one-step, one-dependency process.&lt;/p&gt;

&lt;p&gt;In the rest of this article we will refer to this local environment as http://0.0.0.0:YOUR_PORT; always substitute your own port number (in our example 32769) for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YOUR_PORT&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;introducing-pa11y&quot;&gt;Introducing Pa11y&lt;/h2&gt;

&lt;p&gt;We will use a Dockerized version of Pa11y, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dcycle/pa11y&lt;/code&gt;, here is how it works against, say, amazon.com:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run --rm dcycle/pa11y:1 https://amazon.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;No site that I know of has zero accessibility issues; so you’ll see a bunch of issues in this format:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;• Error: This element&apos;s role is &quot;presentation&quot; but contains child elements with semantic meaning.
  ├── WCAG2AA.Principle1.Guideline1_3.1_3_1.F92,ARIA4
  ├── #navFooter &amp;gt; div:nth-child(2)
  └── &amp;lt;div class=&quot;navFooterVerticalColumn navAccessibility&quot; role=&quot;presentation&quot;&amp;gt;&amp;lt;div class=&quot;navFooterVerticalRo...&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;running-pa11y-against-a-local-site&quot;&gt;Running Pa11y against a local site&lt;/h2&gt;

&lt;p&gt;Developers and continuous integration servers will need to run Pa11y against a local site. We would be tempted to run Pa11y on 0.0.0.0:YOUR_PORT, but that won’t work because Pa11y is being run inside its own container and will not have access to the host machine. You could give it access, but that raises another issue: the port is not guaranteed to be the same at every run, which requires ugly logic to figure out the port. Ugh! Instead, we will attach Pa11y to the Docker network used by our Starter site, in this case called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;starterkit_drupal8site_default&lt;/code&gt; (you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker network ls&lt;/code&gt; to list networks). Because our docker-compose.yml file defines the Drupal container as having the name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupal&lt;/code&gt; and port 80 (the default port), we can now run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run --network starterkit_drupal8site_default \
  --rm dcycle/pa11y:1 http://drupal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This has some errors, just as we expected. Before doing anything else, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo $?&lt;/code&gt;; this will give a non-zero code, meaning running this will make your continuous integration script fail. However, because we decided earlier that we will tolerate, for now, 6 errors on the home page, let’s set a threshold of 6 (or however many errors you get – there are 6 at the time of this writing) instead of the default zero:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run --network starterkit_drupal8site_default \
  --rm dcycle/pa11y:1 http://drupal --threshold 6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo $?&lt;/code&gt; right after, you should get the “passing” exit code of zero. There, we’ve met our threshold, so we will not have a failure!&lt;/p&gt;

&lt;h2 id=&quot;how-about-pages-where-you-need-to-be-logged-in&quot;&gt;How about pages where you need to be logged in?&lt;/h2&gt;

&lt;p&gt;The above solution breaks down, though, when you want to test http://drupal/node/1/edit. Although it will produce results, what we are actually checking against here is the “Access denied” page, not /node/1/edit when we are logged in. We will approach this in the following way:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Set a random password for user 1;&lt;/li&gt;
  &lt;li&gt;Use Puppeteer (see “Tools”, above) to click around your local site with its dummy data, do whatever you want to, and, every step of the way, save the DOM (the document object model, or the current markup after it has been processed by Javascript) as a temporary flat file, named, say, http://drupal/dom-captures/user.html;&lt;/li&gt;
  &lt;li&gt;Use Pa11y to test the temporary file we just created.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h2&gt;

&lt;p&gt;In our Drupal 8 Starterkit, we can test the entire process. Start by running the Puppeteer script:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./scripts/end-to-end-tests.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What does this look like?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/scripts/end-to-end-tests.sh&quot;&gt;Here is our end-to-end testing script&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/tests/browser-tests/test01.js&quot;&gt;Here is our Puppeteer script&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Astute readers have realized that using Puppeteer to click through the site to create our dom captures has the added benefit of confirming that our site functionality works as expected, which is why I called the script &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;end-to-end-tests.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To confirm this actually worked, you can visit, &lt;em&gt;in an incognito window&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;http://0.0.0.0:PORT/dom-captures/user.html&lt;/li&gt;
  &lt;li&gt;http://0.0.0.0:PORT/dom-captures/node-1-edit.html&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes it &lt;em&gt;looks&lt;/em&gt; like you’re logged in, but you are not: these are anonymous webpages which Pa11y can check.&lt;/p&gt;

&lt;p&gt;So if this worked correctly (and it should, because we hav it &lt;a href=&quot;https://circleci.com/gh/dcycle/starterkit-drupal8site/tree/master&quot;&gt;under continuous integration&lt;/a&gt;), we can run our Pa11y tests agains all these pages:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./scripts/a11y-tests.sh
echo $?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/scripts/a11y-tests.sh&quot;&gt;And here is what our accessibility testing script looks like&lt;/a&gt;;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will see the errors, but because the &lt;em&gt;number of errors&lt;/em&gt; is below our threshold, the &lt;em&gt;exit code&lt;/em&gt; will be zero, allowing our Continuous integration tests to pass.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Making a site accessible is, in my opinion, akin to making a site secure: it is not something to add to a to-do list, but rather an approach including all site stakeholders. Neither is accessibility something which can be automated; it really is a team culture. However, approaches like the one outlined in this article, or whatever works in your organization, will give teams metrics to facilitate the integration of accessibility into their day-to-day operations.&lt;/p&gt;

</description>
        
          <description>&lt;p&gt;Accessibility tests can be automated to a degree, but not completely; to succeed at accessibility, it needs to be a mindset shared by developers, UX and front-end folks, business people and other stakeholders. In this article, we will attempt to run tests and produce meaningful metrics which can help teams who are already committed to produce more accessible websites.&lt;/p&gt;

</description>
        
        <pubDate>Sun, 07 Apr 2019 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2019-04-07/accessibility/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2019-04-07/accessibility/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Debug outgoing emails with Mailhog, a dummy mailserver with a GUI</title>
        <description>&lt;p&gt;Often, during local Drupal development (or if we’re really unlucky, in production), we get the dreaded message, “Unable to send e-mail. Contact the site administrator if the problem persists.”&lt;/p&gt;

&lt;p&gt;This can make it hard to debug anything email-related during local development.&lt;/p&gt;

&lt;h2 id=&quot;enter-mailhog&quot;&gt;Enter Mailhog&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/mailhog/MailHog&quot;&gt;Mailhog&lt;/a&gt; is a dummy SMTP server with a browser GUI, which means you view all outgoing messages with a Gmail-type interface.&lt;/p&gt;

&lt;p&gt;It is a major pain to install, but we can automate the entire process with the magic of Docker.&lt;/p&gt;

&lt;p&gt;Let’s see how it works, and discuss after. Follow along by installing &lt;a href=&quot;https://www.docker.com/products/docker-desktop&quot;&gt;Docker Desktop&lt;/a&gt; – &lt;strong&gt;no other dependencies are required&lt;/strong&gt; – and installing &lt;a href=&quot;http://github.com/dcycle/starterkit-drupal8site&quot;&gt;a Drupal 8 starterkit&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/dcycle/starterkit-drupal8site.git
cd starterkit-drupal8site
./scripts/deploy.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will install the following Docker containers: a MySQL server with a starter database, a configured Drupal site, and Mailhog. You wil see something like this at the end of the output:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;If all went well you can now access your site at:

=&amp;gt; Drupal: http://0.0.0.0:32791/user/reset/...
=&amp;gt; Dummy email client: http://0.0.0.0:32790
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;You might be seeing different port numbers instead of 32791 and 32790, so use your own instead of the example ports.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;now-the-magic&quot;&gt;Now, the magic&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Visit http://0.0.0.0:DRUPAL_PORT/user/password&lt;/li&gt;
  &lt;li&gt;Enter “admin” and submit&lt;/li&gt;
  &lt;li&gt;Now visit http://0.0.0.0:MAILHOG_PORT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(In my example, DRUPAL_PORT is 32791 and MAILHOG_PORT is 32790. In your case it will probably be different.)&lt;/p&gt;

&lt;p&gt;As you can see, all emails produced by Drupal are now visible on a cool GUI!&lt;/p&gt;

&lt;h2 id=&quot;so-how-does-it-work&quot;&gt;So how does it work?&lt;/h2&gt;

&lt;p&gt;A dedicated “Mailhog” docker container, using on the &lt;a href=&quot;https://hub.docker.com/r/mailhog/mailhog/&quot;&gt;Mailhog Docker image&lt;/a&gt; is defined in &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/docker-compose.yml#L5-L9&quot;&gt;our docker-compose.yml file&lt;/a&gt;. It exposes port 8025 for public GUI access, which is mapped to a random unused port on the host computer (in the above example, 32790). Port 1025 is the SMTP mailhog port &lt;a href=&quot;https://hub.docker.com/r/mailhog/mailhog/Dockerfile&quot;&gt;as you can see in the Mailhog Dockerfile&lt;/a&gt;. We are not mapping port 1025 to a random port on the host computer because it’s only needed in the Drupal container, not the host machine.&lt;/p&gt;

&lt;p&gt;In the same docker-compose.yml, the “drupal” container (service) &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/docker-compose.yml#L33&quot;&gt;defines a link to the “mail” service&lt;/a&gt;; this means that when you are inside the Drupal container, you can access Mailhog SMPT server “mail” at port 1025.&lt;/p&gt;

&lt;p&gt;In the Starterkit’s &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/Dockerfile#L59&quot;&gt;Dockerfile&lt;/a&gt;, we download the &lt;a href=&quot;https://www.drupal.org/project/smtp&quot;&gt;SMTP&lt;/a&gt; modules, and in &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/drupal/config/core.extension.yml#L36&quot;&gt;our configuration&lt;/a&gt;, we install SMTP (0, in this case, is the module’s weight, it doesn’t mean “disabled”!).&lt;/p&gt;

&lt;p&gt;Next, configuration: because this is for local development, &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/drupal/config/smtp.settings.yml#L1&quot;&gt;we are leaving SMTP off in the exported configuration&lt;/a&gt;; in production we don’t want SMTP to link to Mailhog. Then, in &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/drupal/settings/local-settings.php#L22-L31&quot;&gt;our overridden settings&lt;/a&gt;, we enable SMTP and set the server to “mail” and the port to 1025.&lt;/p&gt;

&lt;p&gt;Now, you can debug sent emails in a very realistic way!&lt;/p&gt;

&lt;p&gt;You can remove the starterkit environment by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker-compose down -v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        
          <description>&lt;p&gt;Often, during local Drupal development (or if we’re really unlucky, in production), we get the dreaded message, “Unable to send e-mail. Contact the site administrator if the problem persists.”&lt;/p&gt;

</description>
        
        <pubDate>Thu, 14 Mar 2019 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2019-03-14/mailhog/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2019-03-14/mailhog/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Local development using Docker Compose and HTTPS</title>
        <description>&lt;p&gt;This article discusses how to use HTTPS for local development if you use Docker and Docker Compose to develop Drupal 7 or Drupal 8 (indeed any other platform as well) projects. We’re assuming you already have a technique to deploy your code to production (either a build step, rsync, etc.).&lt;/p&gt;

&lt;p&gt;In this article we will use &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site&quot;&gt;the Drupal 8 site starterkit&lt;/a&gt;, a Docker Compose-based Drupal application that comes with everything you need to build a Drupal site with a few commands (including local HTTPS); we’ll then discuss how HTTPS works.&lt;/p&gt;

&lt;p&gt;If you want to follow along, install and launch the latest version of Docker, make sure ports 80 and 443 are not used locally, and run these commands:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ~/Desktop
git clone https://github.com/dcycle/starterkit-drupal8site.git
cd starterkit-drupal8site
./scripts/https-deploy.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The script will prompt you for a domain (for example my-website.local) to access your local development environment. You might also be asked for your password if you want the script to add “127.0.0.1 my-website.local” to your /etc/hosts file. (If you do not want to supply your password, you can add that line to /etc/hosts before running ./scripts/https-deploy.sh).&lt;/p&gt;

&lt;p&gt;After a few minutes you will be able to access a Drupal environment on http://my-website.local and https://my-website.local. For https, you will need to explicitly accept the certificate in the browser, because it’s self-signed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Troubleshooting: if you get a connection error, try using an incongnito (private) window in your browser, or a different browser.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Being a security-conscious developer, you probably read through  &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/scripts/https-deploy.sh&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/https-deploy.sh&lt;/code&gt;&lt;/a&gt; before running it on your computer. If you haven’t, you are encouraged to do so now, as we will be explaining how it works in this article.&lt;/p&gt;

&lt;h2 id=&quot;you-cannot-use-lets-encrypt-locally&quot;&gt;You cannot use Let’s Encrypt locally&lt;/h2&gt;

&lt;p&gt;I often see questions related to setting up Let’s Encrypt for local development. This is not possible because the idea behind Let’s Encrypt is to certify that you own the domain on which you’re working; because no one uniquely owns &lt;em&gt;localhost&lt;/em&gt;, or &lt;em&gt;my-project.local&lt;/em&gt;, no one can get a certificate for it.&lt;/p&gt;

&lt;p&gt;For local development, the Let’s Encrypt folks suggest using &lt;a href=&quot;https://letsencrypt.org/docs/certificates-for-localhost/&quot;&gt;trusted, self-signed certificates instead&lt;/a&gt;, which is what we are doing in our script.&lt;/p&gt;

&lt;p&gt;(If you are interested in setting up Let’s Encrypt for a publicly-available domain, this article is not for you. You might be interested, instead, in &lt;a href=&quot;https://blog.dcycle.com/blog/170a6078/letsencrypt-drupal-docker/&quot;&gt;Letsencrypt HTTPS for Drupal on Docker&lt;/a&gt; and &lt;a href=&quot;http://blog.dcycle.com/blog/7f3ea9e1/letsencrypt-docker-compose/&quot;&gt;Deploying Letsencrypt with Docker-Compose&lt;/a&gt;.)&lt;/p&gt;

&lt;h2 id=&quot;make-sure-your-project-works-without-https-first&quot;&gt;Make sure your project works &lt;em&gt;without&lt;/em&gt; https first&lt;/h2&gt;

&lt;p&gt;So let’s look at how the &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/scripts/https-deploy.sh&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/https-deploy.sh&lt;/code&gt;&lt;/a&gt; script we used above works.&lt;/p&gt;

&lt;p&gt;Let’s start by making sure our project works without https, then add a https access in a separate container.&lt;/p&gt;

&lt;p&gt;In our starterkit project, you can run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./scripts/deploy.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At the end of that scripts, you will see something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;If all went well you can now access your site at:

 =&amp;gt; http://0.0.0.0:32780/user/reset/...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Docker is serving our application using a random non-secure port, in this case 32780, and mapping it to port 80 on our container.&lt;/p&gt;

&lt;p&gt;If you use Docker Compose for local development, you might have several applications running at the same time on different host ports, all mapped to port 80 on their respective container. At the end of this article you should be able to see each of them on port 443, something like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;https://my-application-one.local&lt;/li&gt;
  &lt;li&gt;https://my-application-two.local&lt;/li&gt;
  &lt;li&gt;https://my-application-three.local&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The secret to all your local projects sharing port 443 is a reverse proxy container which receives requests to port 443, and indeed port 80 also, and acts as a sort of traffic cop to direct traffic the appropriate container.&lt;/p&gt;

&lt;p&gt;That is why your individual projects should not directly use ports 80 and/or 443.&lt;/p&gt;

&lt;h2 id=&quot;adding-an-nginx-proxy-container-in-front-of-your-projects-container&quot;&gt;Adding an Nginx proxy container in front of your project’s container&lt;/h2&gt;

&lt;p&gt;An oft-seen approach to making your project available locally via HTTPS is to fiddle with &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/Dockerfile&quot;&gt;your Dockerfile&lt;/a&gt;, installing openssl, setting up the certificate there; and rebuilding your container. This can work, but I would argue that it has significant drawbacks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If you have several projects running on https port 443 locally, you could only develop one at a time because you only have one 443 port on your host machine.&lt;/li&gt;
  &lt;li&gt;You would need to maintain the SSL portion of your code for each of your projects.&lt;/li&gt;
  &lt;li&gt;It would go against the principle of &lt;a href=&quot;https://devops.stackexchange.com/questions/447/why-it-is-recommended-to-run-only-one-process-in-a-container&quot;&gt;separation of concerns&lt;/a&gt; which makes containers so robust.&lt;/li&gt;
  &lt;li&gt;You would be reinventing the wheel: there’s already a &lt;a href=&quot;https://github.com/jwilder/nginx-proxy&quot;&gt;well-maintained Nginx proxy image&lt;/a&gt; which does exactly what you want.&lt;/li&gt;
  &lt;li&gt;Your job as a software developer is not to set up SSL.&lt;/li&gt;
  &lt;li&gt;If you decide to deploy your project to production Kubernetes cluster, it would longer makes sense for each of your Apache containers to support SSL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For all those reasons, we will loosely couple our project with the act of serving it via HTTPS; we’ll leave our project alone and place an Nginx proxy in front of it to deal with the SSL/HTTPS portion of our local deployment.&lt;/p&gt;

&lt;h2 id=&quot;local-https-for-one-or-more-running-projects&quot;&gt;Local https for one or more running projects&lt;/h2&gt;

&lt;p&gt;In this example we set up only one starterkit application, but real-world developers often need HTTPS with more than one application. Because &lt;strong&gt;you only have one local 443 port&lt;/strong&gt; for HTTPS, We need a way to differentiate between our running applications.&lt;/p&gt;

&lt;p&gt;Our approach will be for each of our projects to have an assigned local domain. This is why the https script we used in our example asked you to choose a domain like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;starterkit-drupal8.local&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our script stored this information in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt; file at the root or your project, and also made sure it resolves to localhost in your /etc/hosts file.&lt;/p&gt;

&lt;h2 id=&quot;launching-the-nginx-reverse-proxy&quot;&gt;Launching the Nginx reverse proxy&lt;/h2&gt;

&lt;p&gt;To me the terms “proxy” and “reverse proxy” are not intuitive. I’ll try to demystify them here.&lt;/p&gt;

&lt;p&gt;The term “proxy” means something which represents something else; that term is already widely used to denote a web client being hidden from the user. So, a server might deliver content to a proxy which then delivers it to the end user, thereby &lt;em&gt;hiding the end user from the server&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In our case we want to do the reverse: the client (you) is not placing a proxy in front of it; rather the &lt;em&gt;application&lt;/em&gt; is placing a proxy in front of it, thereby &lt;em&gt;hiding the project server from the browser&lt;/em&gt;: the browser communicates with Nginx, and Nginx communicates with your project.&lt;/p&gt;

&lt;p&gt;Hence, “reverse proxy”.&lt;/p&gt;

&lt;p&gt;Our reverse proxy uses &lt;a href=&quot;https://github.com/jwilder/nginx-proxy&quot;&gt;a widely used and well-maintained GitHub project&lt;/a&gt;. The script you used earlier in this article launched a container based on that image.&lt;/p&gt;

&lt;h2 id=&quot;linking-the-reverse-proxy-to-our-application&quot;&gt;Linking the reverse proxy to our application&lt;/h2&gt;

&lt;p&gt;With our starterkit application running on a random port (something like 32780) and our nginx proxy application running on ports 80 and 443, how are the two linked?&lt;/p&gt;

&lt;p&gt;We now need to tell our Nginx proxy that when it receives a request for domain starterkit-drupal8.local, it should display our starterkit application.&lt;/p&gt;

&lt;p&gt;There are a few steps to this, most handled by our script:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Your project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; file &lt;a href=&quot;https://github.com/dcycle/starterkit-drupal8site/blob/master/docker-compose.yml&quot;&gt;should look something like this&lt;/a&gt;: it needs to contain the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VIRTUAL_HOST=${VIRTUAL_HOST}&lt;/code&gt;. This takes the VIRTUAL_HOST environment variable that our script added to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./.env&lt;/code&gt; file, and makes it available inside the container.&lt;/li&gt;
  &lt;li&gt;Our script assumes that your project contains a &lt;a href=&quot;(https://github.com/dcycle/starterkit-drupal8site/blob/master/scripts/deploy.sh)&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/deploy.sh&lt;/code&gt;&lt;/a&gt; file, which deploys our project to a random, non-secure port.&lt;/li&gt;
  &lt;li&gt;Our script assumes that only the Nginx Proxy container is published on ports 80 and 443, so if these ports are already used by something else, you’ll get an error.&lt;/li&gt;
  &lt;li&gt;Our script appends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VIRTUAL_HOST=starterkit-drupal8.local&lt;/code&gt; to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./.env&lt;/code&gt; file.&lt;/li&gt;
  &lt;li&gt;Our script attempts to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1 starterkit-drupal8.local&lt;/code&gt; to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; file, which might require a password.&lt;/li&gt;
  &lt;li&gt;Our script finds the network your project is running on locally (all Docker-compose projects run on their own local named network), and gives the reverse proxy accesss to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;thats-it&quot;&gt;That’s it!&lt;/h2&gt;

&lt;p&gt;You should now be able to access your project locally with https://starterkit-drupal8.local (port 443) &lt;em&gt;and&lt;/em&gt; http://starterkit-drupal8.local (port 80), and apply this technique to any number of Docker Compose projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Troubleshooting: if you get a connection error, try using an incongnito (private) window in your browser, or a different browser; also note that you need to explicitly trust the certificate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can copy paste the script to your Docker Compose project at ./scripts/https-deploy.sh &lt;em&gt;if&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Your ./docker-compose.yml contains the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VIRTUAL_HOST=${VIRTUAL_HOST}&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;You have a script, ./scripts/deploy.sh, which launches a non-secure version of your application on a random port.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;This article discusses how to use HTTPS for local development if you use Docker and Docker Compose to develop Drupal 7 or Drupal 8 (indeed any other platform as well) projects. We’re assuming you already have a technique to deploy your code to production (either a build step, rsync, etc.).&lt;/p&gt;

</description>
        
        <pubDate>Sat, 27 Oct 2018 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2018-10-27/local-https-docker-compose/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2018-10-27/local-https-docker-compose/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>HTTPS on Acquia stage environments with LetsEncrypt, semi-automated</title>
        <description>&lt;p&gt;I recently ran into a series of weird issues on my Acquia production environment which I traced back to some code I deployed which depended on my site being served securely using HTTPS.&lt;/p&gt;

&lt;p&gt;Acquia Staging environments don’t use HTTPS by default and require you to install SSL certificates using a tedious manual process, which in my opinion is outdated, because competitors such as &lt;a href=&quot;https://docs.platform.sh/configuration/routes/https.html&quot;&gt;Platform.sh&lt;/a&gt; and &lt;a href=&quot;https://pantheon.io/features/managed-https&quot;&gt;Pantheon&lt;/a&gt;, &lt;a href=&quot;https://www.drupal.org/project/hosting_https&quot;&gt;Aegir&lt;/a&gt;, even &lt;a href=&quot;https://blog.github.com/2018-05-01-github-pages-custom-domains-https/&quot;&gt;Github pages&lt;/a&gt; support lots of automation around HTTPS using Let’s Encrypt.&lt;/p&gt;

&lt;p&gt;Anyhow, because staging did not have HTTPS, I could not test some code I deployed, which ended up costing me an evening debugging an outage on a production environment. (Any difference between environments will &lt;em&gt;eventually&lt;/em&gt; result in an outage.)&lt;/p&gt;

&lt;p&gt;I found a great blog post which explains how to set up Let’s Encrypt on Acquia environments, &lt;a href=&quot;https://redfinsolutions.com/blog/installing-free-lets-encrypt-ssl-certificates-acquia&quot;&gt;Installing (FREE) Let’s Encrypt SSL Certificates on Acquia, by Chris at Redfin solutions, May 2, 2017&lt;/a&gt;. Although the process is very well documented, I made some tweaks:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, I prefer using Docker-based solutions rather than install softward on my computer. So, instead of install &lt;a href=&quot;https://certbot.eff.org&quot;&gt;certbot&lt;/a&gt; on my Mac, I opted to use the &lt;a href=&quot;https://hub.docker.com/r/certbot/certbot/&quot;&gt;Certbot Docker Image&lt;/a&gt;, this has two advantages for me: first, I don’t need to install certbot on every machine I use this script on; and second, I don’t need to worry about updating certbot, as the Docker image is updated automatically. Of course, this does require that you install Docker on your machine.&lt;/li&gt;
  &lt;li&gt;Second, I automated everything I could. This result in &lt;a href=&quot;https://gist.github.com/alberto56/80c418c656bdf218cae663c3ba227e9a&quot;&gt;this gist&lt;/a&gt; (a “gist” a basically a single file hosted on Github), a script which you can install locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;running-the-script&quot;&gt;Running the script&lt;/h2&gt;

&lt;p&gt;When you put the script locally on your computer (I added it to my project code), at, say &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/set-up-letsencrypt-acquia-stage.sh&lt;/code&gt;, and run it:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;the first time you run it, it will tell you where to put your environment information (in ./acquia-stage-letsencrypt-environments/environment-my-acquia-project-one.source, ./acquia-stage-letsencrypt-environments/environment-my-acquia-project-two.source, etc.), and what to put in those files.&lt;/li&gt;
  &lt;li&gt;the next time you run it, it will automate what it can and tell you exactly what you need to do manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried this and it works for creating new certs, and should work for renewals as well!&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;I recently ran into a series of weird issues on my Acquia production environment which I traced back to some code I deployed which depended on my site being served securely using HTTPS.&lt;/p&gt;

</description>
        
        <pubDate>Fri, 05 Oct 2018 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2018-10-05/https-acquia-stage/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2018-10-05/https-acquia-stage/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Fast-track local Drupal 8 core patch development and testing</title>
        <description>&lt;p&gt;The process documented process for &lt;a href=&quot;https://www.drupal.org/dev-env&quot;&gt;setting up a local environment&lt;/a&gt; and &lt;a href=&quot;https://www.drupal.org/docs/8/phpunit/running-phpunit-tests&quot;&gt;running tests locally&lt;/a&gt; is, in my opinion, so complex that it can be a barrier to even determined developers.&lt;/p&gt;

&lt;p&gt;For those wishing to locally test and develop core patches, I think it is possible to automate the process down to a few steps and few minutes; here is an example with a core issue, &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2273889&quot;&gt;#2273889 Don’t use one language’s plural index formula with another language’s string in the case of untranslated strings using format_plural()&lt;/a&gt;, which, at the time of this writing, results in the number 0 being displayed as 1 in certain cases.&lt;/p&gt;

&lt;p&gt;Is it possible to start useful local development on this within 10 minutes on a computer with nothing installed except Docker? Let’s try…&lt;/p&gt;

&lt;h2 id=&quot;step-1-install-docker&quot;&gt;Step 1: install Docker&lt;/h2&gt;

&lt;p&gt;Install and launch &lt;a href=&quot;https://store.docker.com/search?offering=community&amp;amp;type=edition&quot;&gt;Docker&lt;/a&gt;. Everything we need, Apache web server, MySql server, Drush, Drupal, will reside on Docker containers, so we won’t need to install anything locally except Docker.&lt;/p&gt;

&lt;h2 id=&quot;step-2-launch-a-dev-environment&quot;&gt;Step 2: launch a dev environment&lt;/h2&gt;

&lt;p&gt;I have create a &lt;a href=&quot;https://github.com/dcycle/drupal8_core_dev_helper&quot;&gt;project hosted on GitHub&lt;/a&gt; which will help you set up everything you need in Docker contains without local dependencies other than Docker, or any manual steps. Set it up by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/dcycle/drupal8_core_dev_helper.git &amp;amp;&amp;amp; \
  cd drupal8_core_dev_helper &amp;amp;&amp;amp; \
  ./scripts/deploy.sh`
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will create everything you need: a webserver container and database container, and your Drupal core code which will be placed in ./drupal8_core_dev_helper/drupal; near the end of the output of ./scripts/deploy.sh, you will see &lt;strong&gt;a login link to your development environment&lt;/strong&gt;. Confirm you can access that local development environment at an address like http://0.0.0.0:SOME-PORT. (The port is random.)&lt;/p&gt;

&lt;p&gt;The first time you run this, it will have to download Docker images with Drupal, MySQL, and install everything you need for local development. Future runs will be a lot faster.&lt;/p&gt;

&lt;p&gt;See the &lt;a href=&quot;https://github.com/dcycle/drupal8_core_dev_helper&quot;&gt;project’s README&lt;/a&gt; for more details.&lt;/p&gt;

&lt;p&gt;In your dev environment, you can confirm that the problem exists (provided the issue has not yet been fixed) by &lt;strong&gt;following the instructions in the &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2273889&quot;&gt;“To reproduce this problem:”&lt;/a&gt; section of the issue description on your local development environment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Any calls to &lt;strong&gt;drush&lt;/strong&gt; can be run on the Docker container like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker-compose exec drupal /bin/bash -c &apos;drush ...&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker-compose exec drupal /bin/bash -c &apos;drush en locale language -y&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to run drush directly, you can connect to your container like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker-compose exec drupal /bin/bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will result in the following prompt &lt;em&gt;on the container&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@4744431352a1:/var/www/html#
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you can run drush commands directly on the container:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush eval &quot;print_r(\Drupal::translation()-&amp;gt;formatPlural(0, &apos;1 whatever&apos;, &apos;@count whatevers&apos;, array(), array(&apos;langcode&apos; =&amp;gt; &apos;fr&apos;)) . PHP_EOL);&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because the drupal8_core_dev_helper project also pre-installs &lt;a href=&quot;https://www.drupal.org/project/devel&quot;&gt;devel&lt;/a&gt; on your environment, you can also confirm the problem exists by visiting /devel/php and executing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dpm((string) (\Drupal::translation()-&amp;gt;formatPlural(0, &apos;1 whatever&apos;, &apos;@count whatevers&apos;, array(), array(&apos;langcode&apos; =&amp;gt; &apos;fr&apos;))));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Whether you do this by Drush or /devel/php, the result should be the same if the issue has not been resolved: &lt;strong&gt;1 whatever&lt;/strong&gt; instead of &lt;strong&gt;0 whatevers&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;step-3-get-a-local-version-of-the-patch-and-apply-it&quot;&gt;Step 3: get a local version of the patch and apply it&lt;/h2&gt;

&lt;p&gt;In this example, we’ll look at the patch &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2273889#comment-12561748&quot;&gt;in comment #32 of our formatPlural issue, referenced above&lt;/a&gt;. If the issue has been resolved since this blog post has been written, follow along with another patch.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal8_core_dev_helper
curl https://www.drupal.org/files/issues/2018-04-07/2273889-31-core-8.5.x-plural-index-no-test.patch -O
cd ./drupal &amp;amp;&amp;amp; patch -p1 &amp;lt; ../2273889-31-core-8.5.x-plural-index-no-test.patch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You have now patched your local version of Drupal. You can try the “0 whatevers” test again and the bug should be fixed.&lt;/p&gt;

&lt;h2 id=&quot;running-tests&quot;&gt;Running tests&lt;/h2&gt;

&lt;p&gt;Now the real fun begins… and the “fast-track” ends.&lt;/p&gt;

&lt;p&gt;For any patch to be considered for inclusion in Drupal core, it will need to (a) not break existing tests; and (b) provide a test which, without the patch, confirms that the problem exists.&lt;/p&gt;

&lt;p&gt;Let’s &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2273889#comment-12561748&quot;&gt;head back to comment #32 of issue #2273889&lt;/a&gt; and see if our patch is breaking anything. Clicking on “PHP 7 &amp;amp; MySQL 5.5 23,209 pass, 17 fail” will bring us to the &lt;a href=&quot;https://www.drupal.org/pift-ci-job/933418&quot;&gt;test results page&lt;/a&gt;, which at first glance seems indecipherable. You’ll notice that &lt;a href=&quot;https://www.drupal.org/files/issues/2018-04-07/2273889-31-core-8.5.x-plural-index-no-test.patch&quot;&gt;our seemingly simple change to the PluralTranslatableMarkup.php file&lt;/a&gt; is causing a number of tests to fail: HelpEmptyPageTest, EntityTypeTest…&lt;/p&gt;

&lt;p&gt;Let’s start by finding the test which is most likely to be directly related to our change by searching on the &lt;a href=&quot;https://www.drupal.org/pift-ci-job/933418&quot;&gt;test results page&lt;/a&gt; for the string “PluralTranslatableMarkupTest” (this is name of the class we changed, with the word Test appended), which shows that it is failing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Testing Drupal\Tests\Core\StringTranslation\PluralTranslatableMarkupTest
.E
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We need to figure out where that file resides, by typing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd /path/to/drupal8_core_dev_helper/drupal/core
find . -name &apos;PluralTranslatableMarkupTest.php&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This tells us it is at ./tests/Drupal/Tests/Core/StringTranslation/PluralTranslatableMarkupTest.php.&lt;/p&gt;

&lt;p&gt;Because we have a predictable Docker container, we can relatively easily run this test locally:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd /path/to/drupal8_core_dev_helper
docker-compose exec drupal /bin/bash -c &apos;cd core &amp;amp;&amp;amp; \
  ../vendor/bin/phpunit \
  ./tests/Drupal/Tests/Core/StringTranslation/PluralTranslatableMarkupTest.php&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You should now see the test results for only PluralTranslatableMarkupTest:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PHPUnit 6.5.7 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\Core\StringTranslation\PluralTranslatableMarkupTest
.E                                                                  2 / 2 (100%)

Time: 16.48 seconds, Memory: 6.00MB

There was 1 error:

1) Drupal\Tests\Core\StringTranslation\PluralTranslatableMarkupTest::testPluralTranslatableMarkupSerialization with data set #1 (2, &apos;plural 2&apos;)
Error: Call to undefined method Mock_TranslationInterface_4be32af3::getStringTranslation()

/var/www/html/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php:150
/var/www/html/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php:121
/var/www/html/core/tests/Drupal/Tests/Core/StringTranslation/PluralTranslatableMarkupTest.php:31

ERRORS!
Tests: 2, Assertions: 1, Errors: 1.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;How to fix this, indeed &lt;em&gt;whether&lt;/em&gt; this will be fixed, is a whole nother story, a story fraught with dependency injection, mock objects, method stubs… More an adventure, really, than a story. An adventure which deserves to be told, just not right now.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;The process documented process for &lt;a href=&quot;https://www.drupal.org/dev-env&quot;&gt;setting up a local environment&lt;/a&gt; and &lt;a href=&quot;https://www.drupal.org/docs/8/phpunit/running-phpunit-tests&quot;&gt;running tests locally&lt;/a&gt; is, in my opinion, so complex that it can be a barrier to even determined developers.&lt;/p&gt;

</description>
        
        <pubDate>Sat, 07 Apr 2018 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2018-04-07/fast-local-d8-core-patch-dev-testing/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2018-04-07/fast-local-d8-core-patch-dev-testing/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Caching a Drupal 8 REST resource</title>
        <description>&lt;p&gt;Here are a few things I learned about caching for REST resources.&lt;/p&gt;

&lt;p&gt;There are probably better ways to accomplish this, but here is what works for me.&lt;/p&gt;

&lt;p&gt;Let’s say we have a REST resource that looks something like this in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.../my_module/src/Plugin/rest/resource/MyRestResource.php&lt;/code&gt; and we have enabled it using the &lt;a href=&quot;https://www.drupal.org/project/restui&quot;&gt;Rest UI&lt;/a&gt; module and given anonymous users permission to view it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?php

namespace Drupal\my_module\Plugin\rest\resource;

use Drupal\rest\ResourceResponse;

/**
 * This is just an example.
 *
 * @RestResource(
 *   id = &quot;this_is_just_an_example&quot;,
 *   label = @Translation(&quot;Display the title of node 1&quot;),
 *   uri_paths = {
 *     &quot;canonical&quot; = &quot;/api/v1/get&quot;
 *   }
 * )
 */
class MyRestResource extends ResourceBase {

  /**
   * {@inheritdoc}
   */
  public function get() {
    $node = node_load(1);
    $response = new ResourceResponse(
      [
        &apos;title&apos; =&amp;gt; $node-&amp;gt;getTitle(),
        &apos;time&apos; =&amp;gt; time(),
      ]
    );
    return $response;
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, we can visit http://example.localhost/api/v1/get?_format=json and we will see something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;title&quot;:&quot;Some Title&quot;,&quot;time&quot;:1516803204}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Reloading the page, ‘time’ stays the same. That means caching is working; we are not re-computing our Json output each time someone requests it.&lt;/p&gt;

&lt;h2 id=&quot;how-to-invalidate-the-cache-when-the-title-changes&quot;&gt;How to invalidate the cache when the title changes.&lt;/h2&gt;

&lt;p&gt;If we edit node 1 and change its title to, say, “Another title”, and reload http://example.localhost/api/v1/get?_format=json, we’ll see the old title. To make sure the cache is invalidated when this happens, we need to provide &lt;strong&gt;cacheability metadata&lt;/strong&gt; to our response telling it when it needs to be recomputed.&lt;/p&gt;

&lt;p&gt;Our node, when it’s loaded, contains within it all the caching metadata needed to describe when it should be recomputed: when the title changes, when new filters are added to the text format that’s being used, etc. We can add this information to our ResourceResponse like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$response-&amp;gt;addCacheableDependency($node);
return $response;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When we clear our cache with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cr&lt;/code&gt; and reload our page, we’ll see something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;title&quot;:&quot;Another title&quot;,&quot;time&quot;:1516804411}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Even more fun is changing the title of node 1 and reloading our Json page, and seeing the title and time change &lt;em&gt;without clearing the cache&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;title&quot;:&quot;Yet another title&quot;,&quot;time&quot;:1516804481}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;how-to-set-custom-cache-invalidation-events&quot;&gt;How to set custom cache invalidation events&lt;/h2&gt;

&lt;p&gt;Let’s say you want to trigger a cache rebuild for some reason other than those defined by the node itself (title change, etc.).&lt;/p&gt;

&lt;p&gt;A real-world example might be events: an “upcoming events” page should only display events which start later than now. If we invalidate the cache every day, then we’ll never show yesterday’s events in our events feed. Here, we need to add our custom cache invalidation event, in this case “rebuild events feed”.&lt;/p&gt;

&lt;p&gt;For the purpose of this demo, we won’t actually build an events feed, but we’ll see how cron might be able to trigger cache invalidation.&lt;/p&gt;

&lt;p&gt;Let’s add the following code to our response:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
use Drupal\Core\Cache\CacheableMetadata;
...
$response-&amp;gt;addCacheableDependency($node);
$response-&amp;gt;addCacheableDependency(CacheableMetadata::createFromRenderArray([
  &apos;#cache&apos; =&amp;gt; [
    &apos;tags&apos; =&amp;gt; [
      &apos;rebuild-events-feed&apos;,
    ],
  ],
]));
return $response;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This uses Drupal’s &lt;a href=&quot;https://www.drupal.org/docs/8/api/cache-api/cache-tags&quot;&gt;cache tags&lt;/a&gt; concept and tells Drupal that when the cache tag ‘rebuild-events-feed’ is invalidated, all cacheable responses which have that cache tag should be invalidated as well. I prefer this to the ‘max-age’ cache tag because it allows us more fine-grained control over when to invalidate our caches.&lt;/p&gt;

&lt;p&gt;On cron, we could only invalidate ‘rebuild-events-feed’ if events have passed since our last invalidation of that tag, for example.&lt;/p&gt;

&lt;p&gt;For this example, we’ll just invalidate it manually. Clear your cache to begin using the new code (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cr&lt;/code&gt;), then load the page, you will see something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{&quot;hello&quot;:&quot;Yet another title&quot;,&quot;time&quot;:1516805677}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As always, the time remains the same no matter how many times you reload the page.&lt;/p&gt;

&lt;p&gt;Let’s say you are in the midst of a cron run and you have determined that you need to invalidate your cache for response which have the cache tag ‘rebuild-events-feed’, you can run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;\Drupal::service(&apos;cache_tags.invalidator&apos;)-&amp;gt;invalidateTags([&apos;rebuild-events-feed&apos;])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s do it in Drush to see it in action:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush ev &quot;\Drupal::service(&apos;cache_tags.invalidator&apos;)-&amp;gt;\
  invalidateTags([&apos;rebuild-events-feed&apos;])&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ve just invalidated our ‘rebuild-events-feed’ tag and, hence, Responses that use it.&lt;/p&gt;

&lt;h2 id=&quot;the-dreaded-leaked-metadata-error&quot;&gt;The dreaded “leaked metadata” error&lt;/h2&gt;

&lt;p&gt;This one is beyond my competence level, but I wanted to mention it anyway.&lt;/p&gt;

&lt;p&gt;Let’s say you want to output your node’s URL to Json, you might consider computing it using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$node-&amp;gt;toUrl()-&amp;gt;toString()&lt;/code&gt;. This will give us “/node/1”.&lt;/p&gt;

&lt;p&gt;Let’s add it to our code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
&apos;title&apos; =&amp;gt; $node-&amp;gt;getTitle(),
&apos;url&apos; =&amp;gt; $node-&amp;gt;toUrl()-&amp;gt;toString(),
&apos;time&apos; =&amp;gt; time(),
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This results in a very &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2638686&quot;&gt;ugly error which completely breaks your site (at least at the time of this writing)&lt;/a&gt;: “The controller result claims to be providing relevant cache metadata, but leaked metadata was detected. Please ensure you are not rendering content too early.”.&lt;/p&gt;

&lt;p&gt;The problem, it seems, is that Drupal detects that the &lt;a href=&quot;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Url.php/class/Url/8.2.x&quot;&gt;URL object&lt;/a&gt;, like the node we saw earlier, contains its own internal information which tells it when its cache should be invalidated. Converting it to a string prevents the Response from being informed about that information somehow (again, if someone can explain this better than me, please leave a comment), so an exception is thrown.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Url.php/function/Url%3A%3AtoString/8.4.x&quot;&gt;‘toString()’ function&lt;/a&gt; has an optional parameter, “$collect_bubbleable_metadata”, which can be used to get not just a string, but also information about when its cache should be invalidated. In Drush, this will look like something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush ev &apos;print_r(node_load(1)-&amp;gt;toUrl()-&amp;gt;toString(TRUE))&apos;
Drupal\Core\GeneratedUrl Object
(
    [generatedUrl:protected] =&amp;gt; /node/1
    [cacheContexts:protected] =&amp;gt; Array
        (
        )

    [cacheTags:protected] =&amp;gt; Array
        (
        )

    [cacheMaxAge:protected] =&amp;gt; -1
    [attachments:protected] =&amp;gt; Array
        (
        )

)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This changes the return type of toString(), though: toString() no longer returns a string but a &lt;a href=&quot;https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21GeneratedUrl.php/class/GeneratedUrl/8.2.x&quot;&gt;GeneratedUrl&lt;/a&gt;, so this won’t work:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
&apos;title&apos; =&amp;gt; $node-&amp;gt;getTitle(),
&apos;url&apos; =&amp;gt; $node-&amp;gt;toUrl()-&amp;gt;toString(TRUE),
&apos;time&apos; =&amp;gt; time(),
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It gives us the error “Could not normalize object of type Drupal\Core\GeneratedUrl, no supporting normalizer found”.&lt;/p&gt;

&lt;p&gt;ohthehugemanatee &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2638686#comment-12282657&quot;&gt;commented on Drupal.org&lt;/a&gt; on how to fix this. Integrating his suggestion, our code now looks like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$url = $node-&amp;gt;toUrl()-&amp;gt;toString(TRUE);
$response = new ResourceResponse(
  [
    &apos;title&apos; =&amp;gt; $node-&amp;gt;getTitle(),
    &apos;url&apos; =&amp;gt; $url-&amp;gt;getGeneratedUrl(),
    &apos;time&apos; =&amp;gt; time(),
  ]
);
$response-&amp;gt;addCacheableDependency($node);
$response-&amp;gt;addCacheableDependency($url);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will now work as expected.&lt;/p&gt;

&lt;p&gt;With all the fun we’re having, though, let’s take this a step further, let’s say we want to export the feed of frontpage items in our Response:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$url = $node-&amp;gt;toUrl()-&amp;gt;toString(TRUE);
$view = \Drupal\views\Views::getView(&quot;frontpage&quot;); 
$view-&amp;gt;setDisplay(&quot;feed_1&quot;);
$view_render_array = $view-&amp;gt;render();
$rendered_view = render($view_render_array);

$response = new ResourceResponse(
  [
    &apos;title&apos; =&amp;gt; $node-&amp;gt;getTitle(),
    &apos;url&apos; =&amp;gt; $url-&amp;gt;getGeneratedUrl(),
    &apos;view&apos; =&amp;gt; $rendered_view,
    &apos;time&apos; =&amp;gt; time(),
  ]
);
$response-&amp;gt;addCacheableDependency($node);
$response-&amp;gt;addCacheableDependency($url);
$response-&amp;gt;addCacheableDependency(CacheableMetadata::createFromRenderArray($view_render_array));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You will not be surpised to see the “leaked metadata was detected” error again… In fact you have come to love and expect this error at this point.&lt;/p&gt;

&lt;p&gt;Here is where I’m completely out of my league; according to Crell, &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2450993#comment-10084498&quot;&gt;“[i]f you [use render() yourself], you’re wrong and you should fix your code “&lt;/a&gt;, but I’m not sure how to get a rendered view without using render() myself… I’ve implemented a variation on a &lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2638686#comment-12381959&quot;&gt;comment on Drupal.org by mikejw&lt;/a&gt; suggesting using different &lt;em&gt;render context&lt;/em&gt; to prevent Drupal from complaining.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$view_render_array = NULL;
$rendered_view = NULL;
\Drupal::service(&apos;renderer&apos;)-&amp;gt;executeInRenderContext(new RenderContext(), function () use ($view, &amp;amp;$view_render_array, &amp;amp;$rendered_view) {
  $view_render_array = $view-&amp;gt;render();
  $rendered_view = render($view_render_array);
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we check to make sure we have this line in our code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$response-&amp;gt;addCacheableDependency(CacheableMetadata::createFromRenderArray($view_render_array));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;we’re telling our Response’s cache to invalidate whenever our view’s cache invaliates. So, for example, if we have several nodes promoted to the front page in our view, we can modify any one of them and our entire Response’s cache will be invalidated and rebuilt.&lt;/p&gt;

&lt;h2 id=&quot;resources-and-further-reading&quot;&gt;Resources and further reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.stackexchange.com/questions/219239/how-can-i-use-the-same-render-cache-but-for-json&quot;&gt;Stack Exchange Drupal Answers: How can I use the same render cache but for json?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://spinningcode.org/2017/05/cached-json-responses-in-drupal-8/&quot;&gt;Cached JSON responses in Drupal 8, Aaron Crosman, May 6, 2017, Spinning Code blog&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2745475&quot;&gt;Drupal.org issue: Generating cacheable responses results in Logic Exception&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2638686&quot;&gt;Drupal.org Issue: Exception in EarlyRenderingControllerWrapperSubscriber is a DX nightmare, remove it&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/docs/8/api/cache-api/cache-tags&quot;&gt;Cache tags explained&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mediacurrent.com/blog/using-normalizers-alter-rest-json-structure-drupal-8&quot;&gt;Using normalizers to alter REST JSON structure in Drupal 8,, Edward Chan, March 22, 2017, Mediacurrent&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://api.drupal.org/api/drupal/core!modules!rest!src!ModifiedResourceResponse.php/8.2.x&quot;&gt;ModifiedResourceResponse&lt;/a&gt;, to return responses which are never cached.&lt;/li&gt;
&lt;/ul&gt;
</description>
        
          <description>&lt;p&gt;Here are a few things I learned about caching for REST resources.&lt;/p&gt;

</description>
        
        <pubDate>Wed, 24 Jan 2018 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2018-01-24/caching-drupal-8-rest-resource/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2018-01-24/caching-drupal-8-rest-resource/</guid>
        
        <category>planet</category>
        
        <category>blog</category>
        
        
      </item>
      
    
      
      <item>
        <title>Migrating Webforms from Drupal 7 to Drupal 8</title>
        <description>&lt;p&gt;I recently needed to port hundreds of Drupal 7 webforms with thousands of submissions from Drupal 7 to Drupal 8.&lt;/p&gt;

&lt;p&gt;My requirements were:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Node ids need to remain the same&lt;/li&gt;
  &lt;li&gt;Webforms need to be &lt;a href=&quot;https://www.drupal.org/project/webform/issues/2931104&quot;&gt;treated as data&lt;/a&gt;: they should be ignored by config export and import, just like nodes and taxonomy terms are. The reasonining is that in my setup, forms are managed by site editors, not developers. (This is not related to migration per se, but was a success criteria for my migration so I’ll document my solution here)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;migration-from-drupal-7&quot;&gt;Migration from Drupal 7&lt;/h2&gt;

&lt;p&gt;I could not find a reliable upgrade or migration path from Drupal 7 to Drupal 8. I found &lt;a href=&quot;https://www.drupal.org/project/webform_migrate&quot;&gt;webform_migrate&lt;/a&gt; lacks documentation (I don’t know where to start) and &lt;a href=&quot;https://www.drupal.org/project/migrate_webform&quot;&gt;migrate_webform&lt;/a&gt; is &lt;a href=&quot;https://www.drupal.org/project/migrate_webform/issues/2279477&quot;&gt;meant for Drupal 6, not Drupal 7 as a source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I settled on a my own combination of tools and workflows to perform the migration, all of them available on my Github account.&lt;/p&gt;

&lt;p&gt;Using version 8.x-5.x of &lt;a href=&quot;https://www.drupal.org/project/webform&quot;&gt;webform&lt;/a&gt;, I started by enabling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webform&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webform_node&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webform_ui&lt;/code&gt; on my Drupal 8 site, this gives me an empty webform node type.&lt;/p&gt;

&lt;p&gt;I then followed the instructions for a basic migration, which is outside the scope of this article. I have a &lt;a href=&quot;https://github.com/dcycle/d6_to_d8_migration_example/tree/7&quot;&gt;project on Github&lt;/a&gt; which I use as starting point from my Drpual 6 and 7 to 8 migrations. The blog post &lt;a href=&quot;https://drupalize.me/blog/201604/custom-drupal-drupal-migrations-migrate-tools&quot;&gt;Custom Drupal-to-Drupal Migrations with Migrate Tools, Drupalize.me, April 26, 2016 by William Hetherington&lt;/a&gt; provides more information on performing a basic migration of data.&lt;/p&gt;

&lt;p&gt;Once you have set up your migration configurations as per those instructions, you should be able to run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush migrate-import upgrade_d7_node_webform --execute-dependencies
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And you should see something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Processed 25 items (25 created, 0 updated, 0 failed, 0 ignored) - done with &apos;upgrade_d7_node_type&apos;
Processed 11 items (11 created, 0 updated, 0 failed, 0 ignored) - done with &apos;upgrade_d7_user_role&apos;
Processed 0 items (0 created, 0 updated, 0 failed, 0 ignored) - done with &apos;upgrade_d7_user_role&apos;
Processed 95 items (95 created, 0 updated, 0 failed, 0 ignored) - done with &apos;upgrade_d7_user&apos;
Processed 109 items (109 created, 0 updated, 0 failed, 0 ignored) - done with &apos;upgrade_d7_node_webform&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point I had all my webforms as nodes with the same node ids on Drupal 7 and Drupal 8, however this does nothing to import the actual forms or submissions.&lt;/p&gt;

&lt;h2 id=&quot;importing-the-data-itself&quot;&gt;Importing the data itself&lt;/h2&gt;

&lt;p&gt;I found that the most efficient way of importing the data was to create my own Drupal 8 module, which &lt;a href=&quot;https://github.com/dcycle/webform_d7_to_d8&quot;&gt;I have published on Dcycle’s Github account&lt;/a&gt;, called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webform_d7_to_d8&lt;/code&gt;. (I have decided against publishing this on Drupal.org because I don’t plan on maintaining it long-term, and I don’t have the resources to combine efforts with existing webform migration modules.)&lt;/p&gt;

&lt;p&gt;I did my best to make that module self-explanatory, so you should be able to follow the steps the &lt;a href=&quot;https://github.com/dcycle/webform_d7_to_d8&quot;&gt;README file&lt;/a&gt;, which I will summarize here:&lt;/p&gt;

&lt;p&gt;Start by giving your Drupal 8 site access to your Drupal 7 database in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./sites/default/settings.php&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$databases[&apos;upgrade&apos;][&apos;default&apos;] = array (
  &apos;database&apos; =&amp;gt; &apos;drupal7database&apos;,
  &apos;username&apos; =&amp;gt; &apos;drupal7user&apos;,
  &apos;password&apos; =&amp;gt; &apos;drupal7password&apos;,
  &apos;prefix&apos; =&amp;gt; &apos;&apos;,
  &apos;host&apos; =&amp;gt; &apos;drupal7host&apos;,
  &apos;port&apos; =&amp;gt; &apos;3306&apos;,
  &apos;namespace&apos; =&amp;gt; &apos;Drupal\\Core\\Database\\Driver\\mysql&apos;,
  &apos;driver&apos; =&amp;gt; &apos;mysql&apos;,
);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run the migration with our without options:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush ev &apos;webform_d7_to_d8()&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush ev &apos;webform_d7_to_d8([&quot;nid&quot; =&amp;gt; 123])&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush ev &apos;webform_d7_to_d8([&quot;simulate&quot; =&amp;gt; TRUE])&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;…&lt;/p&gt;

&lt;p&gt;More detailed information can be found in the module’s &lt;a href=&quot;https://github.com/dcycle/webform_d7_to_d8&quot;&gt;README file&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;treating-webforms-as-data&quot;&gt;Treating webforms as data&lt;/h2&gt;

&lt;p&gt;Once you have imported your webforms to Drupal 8, they are treated as configuration, that is, the Webform module assumes that developers, not site builders, will be creating the forms. This may be fine in many cases, however my usecase is that site editors want to create and edit forms directly on the production site, and we don’t want them to be tracked by the configuration management system.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.drupal.org/u/jrockowitz&quot;&gt;Jacob Rockowitz&lt;/a&gt; &lt;a href=&quot;https://www.drupal.org/project/webform/issues/2931104&quot;&gt;pointed me in the right direction&lt;/a&gt; for making sure webforms are not treated as configuration. For that purpose I am using &lt;a href=&quot;https://github.com/previousnext/drush_cmi_tools&quot;&gt;Drush CMI tools&lt;/a&gt; by Previous Next and documented on their blog post, &lt;a href=&quot;https://www.previousnext.com.au/blog/introducing-drush-cmi-tools&quot;&gt;Introducing Drush CMI tools, 24 Aug. 2016&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you install Drush CMI tools in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.drush&lt;/code&gt; folder and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cc drush&lt;/code&gt;, you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;druch cexy&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;druch cimy&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cex&lt;/code&gt; in your conguration management process. Here is how and why:&lt;/p&gt;

&lt;p&gt;Normally, if you develop your site locally and, say, add a content type or field, or remove a content type of field, you can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cex&lt;/code&gt; to export your newly created configuration. Then, your colleagues can pull your code and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim&lt;/code&gt; to pull your configuration. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim&lt;/code&gt; can also be used in continuous integration, preproduction, dev, and production environments.&lt;/p&gt;

&lt;p&gt;The problem is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cex&lt;/code&gt; exports &lt;em&gt;all&lt;/em&gt; configuration, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim&lt;/code&gt; deletes everything in the database which is not in configuration. In our case, we don’t want to consider webforms as configuration but as data, just as nodes as taxonomy terms: we don’t want them to be exported along with other configuration; and if they exist on a target environment we want to leave them as they are.&lt;/p&gt;

&lt;p&gt;Using Drush CMI tools, you can add a file such as the following to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.drush/config-ignore.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# See http://blog.dcycle.com/blog/2017-12-18
ignore:
  - webform.webform.*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This has to be done on all developers’ machines or, if you use Docker, on a shared Docker container (which is outside the scope of this article).&lt;/p&gt;

&lt;p&gt;Now, for exporting configuration, run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush cexy --destination=&apos;/path/to/config/folder&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, webforms will not be exported along with other configuration.&lt;/p&gt;

&lt;p&gt;We also need to avoid erasing webforms on target environments: if you create a webform on a target environment, then run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim&lt;/code&gt;, you will see something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;webform.webform.webform_9521   delete
webform.webform.webform_8996   delete
webform.webform.webform_8991   delete
webform.webform.webform_8986   delete
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, we need to avoid deleting webforms on the target environment when we import configuration. We could just do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim --partial&lt;/code&gt; but this avoids deleting &lt;em&gt;everything&lt;/em&gt;, not just webforms.&lt;/p&gt;

&lt;p&gt;Drush CMI tools provides an alternative:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush cimy --source=/path/to/config/folder
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This works much like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim --partial&lt;/code&gt;, but it allows you to specify another parameter, –delete-list=/path/to/config-delete.yml&lt;/p&gt;

&lt;p&gt;Then, in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config-delete.yml&lt;/code&gt;, you can specify items that you actually want to delete on the target environment, for example content types, fields, and views which do not exist in code. This is dependent on your workflow and they way to set it up isdocumented on the &lt;a href=&quot;https://github.com/previousnext/drush_cmi_tools&quot;&gt;Drush CMI tools project homepage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this in place, we’ll have our Drupal 7 webforms on our Drupal 8 site.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;I recently needed to port hundreds of Drupal 7 webforms with thousands of submissions from Drupal 7 to Drupal 8.&lt;/p&gt;

</description>
        
        <pubDate>Mon, 18 Dec 2017 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2017-12-18/migrating-webforms-drupal7-to-drupal8/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2017-12-18/migrating-webforms-drupal7-to-drupal8/</guid>
        
        <category>planet</category>
        
        <category>blog</category>
        
        
      </item>
      
    
      
      <item>
        <title>Letsencrypt HTTPS for Drupal on Docker</title>
        <description>&lt;p&gt;This article is about serving your Drupal Docker container, and/or any other container, via https with a valid &lt;a href=&quot;https://letsencrypt.org&quot;&gt;Let’s encrypt&lt;/a&gt; SSL certificate.&lt;/p&gt;

&lt;p&gt;Edit: if you’re having trouble with Docker-Compose, read &lt;a href=&quot;http://blog.dcycle.com/blog/7f3ea9e1/letsencrypt-docker-compose/&quot;&gt;this follow-up post&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;step-one-make-sure-you-have-a-public-vm&quot;&gt;Step one: make sure you have a public VM&lt;/h2&gt;

&lt;p&gt;To follow along, create a new virtual machine (VM) with Docker, for example using the “Docker” distribution in the “One-click apps” section of Digital Ocean.&lt;/p&gt;

&lt;p&gt;This will not work on localhost, because in order to use Let’s Encrypt, you need to demonstrate ownership over your domain(s) to the outside world.&lt;/p&gt;

&lt;p&gt;In this tutorial we will serve two different sites, one simple HTML site and one Drupal site, each using standard ports, on the same Docker host, using a &lt;strong&gt;reverse proxy&lt;/strong&gt;, a container which sits in front of your other containers and directs traffic.&lt;/p&gt;

&lt;h2 id=&quot;step-two-set-up-two-domains-or-subdomains-you-own-and-point-them-to-your-server&quot;&gt;Step two: Set up two domains or subdomains you own and point them to your server&lt;/h2&gt;

&lt;p&gt;Start by making sure you have two domains which point to your server, in this example we’ll use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;test-one.example.com will be a simple HTML site.&lt;/li&gt;
  &lt;li&gt;test-two.example.com will be a Drupal site.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;step-three-create-your-sites&quot;&gt;Step three: create your sites&lt;/h2&gt;

&lt;p&gt;We do not want to map our containers’ ports directly to our host ports using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p 80:80 -p 443:443&lt;/code&gt; because we will have more than one app using the same port (the secure 443). Port mapping will be the responsibility of the reverse proxy (more on that later). &lt;strong&gt;Replace example.com with your own domain&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;DOMAIN=example.com
docker run -d \
  -e &quot;VIRTUAL_HOST=test-one.$DOMAIN&quot; \
  -e &quot;LETSENCRYPT_HOST=test-one.$DOMAIN&quot; \
  -e &quot;LETSENCRYPT_EMAIL=my-email@$DOMAIN&quot; \
  --expose 80 --name test-one \
  httpd
docker run -d \
  -e &quot;VIRTUAL_HOST=test-two.$DOMAIN&quot; \
  -e &quot;LETSENCRYPT_HOST=test-two.$DOMAIN&quot; \
  -e &quot;LETSENCRYPT_EMAIL=my-email@$DOMAIN&quot; \
  --expose 80 --name test-two \
  drupal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you have two running sites, but they’re not yet accessible to the outside world.&lt;/p&gt;

&lt;h2 id=&quot;step-three-a-reverse-proxy-and-lets-encrypt&quot;&gt;Step three: a reverse proxy and Let’s encrypt&lt;/h2&gt;

&lt;p&gt;The term “proxy” means something which represents something else. In our case we want to have a webserver container which represents our Drupal and html containers. The Drupal and html containers are effectively hidden in front of a proxy. Why “reverse”? The term “proxy” is already used and means that the web &lt;em&gt;user&lt;/em&gt; is hidden from the server. If it is the web servers that are hidden (in this case Drupal or the html containers), we use the term “reverse proxy”.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://letsencrypt.org&quot;&gt;Let’s encrypt&lt;/a&gt; is a free certificate authority which certifies that you are the owner of your domain.&lt;/p&gt;

&lt;p&gt;We will use &lt;a href=&quot;https://github.com/jwilder/nginx-proxy&quot;&gt;nginx-proxy&lt;/a&gt; as our reverse proxy. Because that does not take care of certificates, we will use &lt;a href=&quot;https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion&quot;&gt;LetsEncrypt companion container for nginx-proxy&lt;/a&gt; to set up and maintain Let’s Encrypt certificates.&lt;/p&gt;

&lt;p&gt;Let’s start by creating an empty directory which will contain our certificates:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir &quot;$HOME&quot;/certs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, following the instructions of the LetsEncrypt companion project, we can set up our reverse proxy:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run -d -p 80:80 -p 443:443 \
  --name nginx-proxy \
  -v &quot;$HOME&quot;/certs:/etc/nginx/certs:ro \
  -v /etc/nginx/vhost.d \
  -v /usr/share/nginx/html \
  -v /var/run/docker.sock:/tmp/docker.sock:ro \
  --label com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy \
  --restart=always \
  jwilder/nginx-proxy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And, finally, start the LetEncrypt companion:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run -d \
  --name nginx-letsencrypt \
  -v &quot;$HOME&quot;/certs:/etc/nginx/certs:rw \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  --volumes-from nginx-proxy \
  --restart=always \
  jrcs/letsencrypt-nginx-proxy-companion
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Wait a few minutes for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;$HOME&quot;/certs&lt;/code&gt; to be populated with your certificate files, and you should now be able to access your sites:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;https://test-two.example.com/ should show the Drupal installer (setting up a MySQL container to actually install Drupal is outside the scope of this article);&lt;/li&gt;
  &lt;li&gt;https://test-one.example.com should show the “It works!” page.&lt;/li&gt;
  &lt;li&gt;In both cases, the certificate should be valid and you should get no error message.&lt;/li&gt;
  &lt;li&gt;http://test-one.example.com should redirect to https://test-one.example.com&lt;/li&gt;
  &lt;li&gt;http://test-two.example.com should redirect to https://test-two.example.com&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;a-note-about-renewals&quot;&gt;A note about renewals&lt;/h2&gt;

&lt;p&gt;Let’s Encrypt certificates &lt;a href=&quot;https://letsencrypt.org/2015/11/09/why-90-days.html&quot;&gt;last 3 months&lt;/a&gt;, so we generally want to renew every two months. &lt;a href=&quot;https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion&quot;&gt;LetsEncrypt companion container for nginx-proxy&lt;/a&gt; states that it automatically renews certificates which are set to expire in less than a month, and it checks this hourly, although there are some renewal-related issues in the &lt;a href=&quot;https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion/issues?utf8=✓&amp;amp;q=renewal&quot;&gt;issue queue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It seems to also be possible to force renewals by running:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker exec nginx-letsencrypt /app/force_renew
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So it might be worth considering to be on the lookout for failed renewals and force them if necessary.&lt;/p&gt;

&lt;h2 id=&quot;edit-domain-specific-configurations&quot;&gt;Edit: domain-specific configurations&lt;/h2&gt;

&lt;p&gt;I used this technique to create a Docker registry, and make it accessible securely:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run \
  --entrypoint htpasswd \
  registry:2 -Bbn username password &amp;gt; auth/htpasswd

docker run -d --expose 5000 \
  -e &quot;VIRTUAL_HOST=mydomain.example.com&quot; \
  -e &quot;LETSENCRYPT_HOST=mydomain.example.com&quot; \
  -e &quot;LETSENCRYPT_EMAIL=me@example.com&quot; \
  -e &quot;REGISTRY_AUTH=htpasswd&quot; \
  -e &quot;REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm&quot; \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ 
  --restart=always -v &quot;$PWD&quot;/auth:/auth \
  --name registry registry:2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But when trying to push an image, I was getting “413 Request Entity Too Large”. This is an error with the nginx-proxy, not the Docker registry. To fix this, you can set domain-specific configurations, in this example we are allowing a maximum of 600M to be passed but only to the Docker registry at mydomain.example.com:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker exec nginx-proxy /bin/bash -c &apos;cp /etc/nginx/vhost.d/default /etc/nginx/vhost.d/mydomain.example.com&apos;
docker exec nginx-proxy /bin/bash -c &apos;echo &quot;client_max_body_size 600M;&quot; &amp;gt;&amp;gt; /etc/nginx/vhost.d/mydomain.example.com&apos;
docker restart nginx-proxy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;edit-reverse-proxy-on-drupal-8-or-9&quot;&gt;Edit: Reverse proxy on Drupal 8 or 9&lt;/h2&gt;

&lt;p&gt;Thanks to @wells on &lt;a href=&quot;https://www.drupal.org/project/social_auth_google/issues/3207114&quot;&gt;this issue&lt;/a&gt; and nitin.k on &lt;a href=&quot;https://www.drupal.org/project/metatag/issues/2842049#comment-13948744&quot;&gt;this issue&lt;/a&gt; for pointing me in the right direction on how Drupal can know its base url should be HTTPS. In order to use modules such as social_auth_google and metatag which require Drupal to know its public URL even if it is behind a reverse proxy, you need to figure out the reverse proxy IP.&lt;/p&gt;

&lt;p&gt;To do so temporarily install devel_php on your site, and then go to /devel/php and enter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpm($_SERVER[&apos;REMOTE_ADDR&apos;]);&lt;/code&gt;. This will give you a result such as 172.18.0.5. It is &lt;em&gt;not&lt;/em&gt; the same IP as what you get when you ping your URL, or when you inspect the headers the reverse proxy sends to Drupal.&lt;/p&gt;

&lt;p&gt;Then add this to your settings, and clear your cache:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$settings[&apos;reverse_proxy&apos;] = TRUE;
$settings[&apos;reverse_proxy_addresses&apos;] = [&apos;172.18.0.5&apos;];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;enjoy&quot;&gt;Enjoy!&lt;/h2&gt;

&lt;p&gt;You can now bask in the knowledge that your cooking blog will not be man-in-the-middled.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;This article is about serving your Drupal Docker container, and/or any other container, via https with a valid &lt;a href=&quot;https://letsencrypt.org&quot;&gt;Let’s encrypt&lt;/a&gt; SSL certificate.&lt;/p&gt;

</description>
        
        <pubDate>Tue, 03 Oct 2017 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/170a6078/letsencrypt-drupal-docker/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/170a6078/letsencrypt-drupal-docker/</guid>
        
        <category>planet</category>
        
        <category>blog</category>
        
        
      </item>
      
    
      
      <item>
        <title>Can the exact same module code run on Drupal 7 and 8?</title>
        <description>&lt;p&gt;As the maintainer of &lt;a href=&quot;http://drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt;, having procrastinated long and hard before releasing a Drupal 8 version, I decided to leave my (admittedly inelegant) logic intact and abstract away the Drupal 7 code, with the goal of plugging in Drupal 7 or 8 code at runtime.&lt;/p&gt;

&lt;h2 id=&quot;example-original-drupal-7-code&quot;&gt;Example original Drupal 7 code&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Some logic.
$updated_file = file_save($drupal_file);
// More logic.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;example-updated-code&quot;&gt;Example updated code&lt;/h2&gt;

&lt;p&gt;Here is a simplified example of how the updated code might look:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// Some logic.
$updated_file = Framework::instance()-&amp;gt;fileSave($drupal_file);
// More logic.

abstract class Framework {

  static function instance() {
    if (!$this-&amp;gt;instance) {
      if (defined(&apos;VERSION&apos;)) {
        $this-&amp;gt;instance = new Drupal7();
      }
      else {
        $this-&amp;gt;instance = new Drupal8();
      }
    }
    return $this-&amp;gt;instance;
  }

  abstract function fileSave($drupal_file);

}

class Drupal8 extends Framework {
  public function fileSave($drupal_file) {
    $drupal_file-&amp;gt;save();
    return $drupal_file;
  }
}

class Drupal7 extends Framework {
  public function fileSave($drupal_file) {
    return file_save($drupal_file);
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once I have defined fileSave(), I can simply replace every instance of file_save() in my legacy code with Framework::instance()-&amp;gt;fileSave().&lt;/p&gt;

&lt;p&gt;In theory, I can then identify all Drupal 7 code my module and abstract it away.&lt;/p&gt;

&lt;h2 id=&quot;automated-testing&quot;&gt;Automated testing&lt;/h2&gt;

&lt;p&gt;As long as I &lt;em&gt;surgically&lt;/em&gt; replace Drupal 7 code such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file_save()&lt;/code&gt; with “universal” code such &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Framework::instance()-&amp;gt;fileSave()&lt;/code&gt;, &lt;em&gt;without doing anything else, without giving in the impulse of “improving” the code&lt;/em&gt;, I can theoretically only test &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Framework::instance()-&amp;gt;fileSave()&lt;/code&gt; itself on Drupal 7 and Drupal 8, and as long as both versions are the same, my underlying code should work. My approach to automated tests is: if it works and you’re not changing it, there is no need to test it.&lt;/p&gt;

&lt;p&gt;Still, I want to make sure my framework-specific code works as expected. To set up my testing environment, I have used Docker-compose to set up three containers: Drupal 7, Drupal 8; and MySQL. I then have a script which builds the sites, installs my module on each, then run a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;selftest()&lt;/code&gt; function which can test the abstracted function such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fileSave()&lt;/code&gt; and make sure they work.&lt;/p&gt;

&lt;p&gt;This can then be &lt;a href=&quot;https://circleci.com/gh/dcycle/realistic_dummy_content&quot;&gt;run on a continuous integration platform such as Circle CI&lt;/a&gt; which generates a cool badge:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://circleci.com/gh/dcycle/realistic_dummy_content&quot;&gt;&lt;img src=&quot;https://circleci.com/gh/dcycle/realistic_dummy_content.svg?style=svg&quot; alt=&quot;CircleCI&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;extending-to-backdrop&quot;&gt;Extending to Backdrop&lt;/h2&gt;

&lt;p&gt;Once your module is structured in this way, it is relatively easy to add new related frameworks, and I’m much more comfortable releasing a Drupal 9 update in 2021 (or whenever it’s ready).&lt;/p&gt;

&lt;p&gt;I have included experimental Backdrop code in Realistic Dummy Content to prove the point. &lt;a href=&quot;https://backdropcms.org&quot;&gt;Backdrop&lt;/a&gt; is a fork of Drupal 7.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;abstract class Framework {
  static function instance() {
    if (!$this-&amp;gt;instance) {
      if (defined(&apos;BACKDROP_BOOTSTRAP_SESSION&apos;)) {
        $this-&amp;gt;instance = new Backdrop();
      }
      elseif (defined(&apos;VERSION&apos;)) {
        $this-&amp;gt;instance = new Drupal7();
      }
      else {
        $this-&amp;gt;instance = new Drupal8();
      }
    }
    return $this-&amp;gt;instance;
  }
}

// Most of Backdrop&apos;s API is identical to D7, so we can only override
// what differs, such as fileSave().
class Backdrop extends Drupal7 {
  public function fileSave($drupal_file) {
    file_save($drupal_file);
    // Unlike Drupal 7, Backdrop returns a result code, not the file itself,
    // in file_save(). We are expecting the file object.
    return $drupal_file;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;disadvantages-of-this-approach&quot;&gt;Disadvantages of this approach&lt;/h2&gt;

&lt;p&gt;Having just released &lt;a href=&quot;http://drupal.org/project/realistic_dummy_content&quot;&gt;Realisic Dummy Content&lt;/a&gt; 7.x-2.0-beta1 and 8.x-2.0-beta1 (which are identical), I can safely say that this approach was a lot more time-consuming than I initially thought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drupal 7 class autoloading&lt;/strong&gt; is incompatible with Drupal 8 autoloading. In Drupal 7, classes cannot (to my knowledge) use namespaces, and must be added to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.info&lt;/code&gt; file, like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;files[] = includes/MyClass.php
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once that is done, you can define MyClass in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;includes/MyClass.php&lt;/code&gt;, then use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyClass&lt;/code&gt; anywhere you want in your code.&lt;/p&gt;

&lt;p&gt;Drupal 8 uses &lt;a href=&quot;https://www.drupal.org/docs/develop/coding-standards/psr-4-namespaces-and-autoloading-in-drupal-8&quot;&gt;PSR-4 autoloading with namespaces&lt;/a&gt;, so I decided to create my own autoloader to use the same system in Drupal 7, something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;spl_autoload_register(function ($class_name) {
  if (defined(&apos;VERSION&apos;)) {
    // We are in Drupal 7.
    $parts = explode(&apos;\\&apos;, $class_name);
    // Remove &quot;Drupal&quot; from the beginning of the class name.
    array_shift($parts);
    $module = array_shift($parts);
    $path = &apos;src/&apos; . implode(&apos;/&apos;, $parts);
    if ($module == &apos;MY_MODULE_NAME&apos;) {
      module_load_include(&apos;php&apos;, $module, $path);
    }
  }
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Hooks&lt;/strong&gt; have different signatures in Drupal 7 and 8; in my case I was lucky and the only hook I need for Drupal 7 and 8 is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_entity_presave()&lt;/code&gt; which has a similar signature and can be abstracted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deeply-nested associative arrays&lt;/strong&gt; are a staple of Drupal 7, so a lot of legacy code expects this type of data. Shoehorning Drupal 8 to output something like Drupal 7’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;field_info_fields()&lt;/code&gt;, for example, was a painful experience:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public function fieldInfoFields() {
  $return = array();
  $field_map = \Drupal::entityManager()-&amp;gt;getFieldMap();
  foreach ($field_map as $entity_type =&amp;gt; $fields) {
    foreach ($fields as $field =&amp;gt; $field_info) {
      $return[$field][&apos;entity_types&apos;][$entity_type] = $entity_type;
      $return[$field][&apos;field_name&apos;] = $field;
      $return[$field][&apos;type&apos;] = $field_info[&apos;type&apos;];
      $return[$field][&apos;bundles&apos;][$entity_type] = $field_info[&apos;bundles&apos;];
    }
  }
  return $return;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, making Drupal 8 work like Drupal 7 makes it hard to use Drupal 8’s advanced features such as Plugins. However, once your module is “universal”, adding Drupal 8-specific functionality might be an option.&lt;/p&gt;

&lt;h2 id=&quot;using-this-approach-for-website-upgrades&quot;&gt;Using this approach for website upgrades&lt;/h2&gt;

&lt;p&gt;This approach might remove a lot of the risk associated with complex site upgrades. Let’s say I have a Drupal 7 site with a few custom modules: each module can be made “universal” in this way. If automated tests are added for all subsequent development, migrating the functionality to Drupal 8 might be less painful.&lt;/p&gt;

&lt;h2 id=&quot;a-fun-proof-of-concept-or-real-value&quot;&gt;A fun proof of concept, or real value?&lt;/h2&gt;

&lt;p&gt;I’ve been toying with this approach for some time, and had a good time (yes, that’s my definition of a good time!) implementing it, but it’s not for everyone or every project. If your usecase includes preserving legacy functionality without leveraging Drupal 8’s modern features, while reducing risk, it can have value though. The jury is still out on whether maintaining a single universal branch will really be more efficient than maintaining two separate branches for Realistic Dummy Content, and whether the approach can reduce risk during site upgrades of legacy custom code, which I plan to try on my next upgrade project.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;As the maintainer of &lt;a href=&quot;http://drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt;, having procrastinated long and hard before releasing a Drupal 8 version, I decided to leave my (admittedly inelegant) logic intact and abstract away the Drupal 7 code, with the goal of plugging in Drupal 7 or 8 code at runtime.&lt;/p&gt;

</description>
        
        <pubDate>Tue, 28 Feb 2017 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/7b285da4/same-module-drupal-7-and-8/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/7b285da4/same-module-drupal-7-and-8/</guid>
        
        <category>planet</category>
        
        <category>blog</category>
        
        
      </item>
      
    
      
      <item>
        <title>When not to use Drupal</title>
        <description>&lt;p&gt;Unless you work exclusively with Drupal developers, you might be hearing some criticism of the Drupal community, among them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We are almost cult-like in our devotion to Drupal;&lt;/li&gt;
  &lt;li&gt;maintenance and hosting are expensive;&lt;/li&gt;
  &lt;li&gt;Drupal is really complicated;&lt;/li&gt;
  &lt;li&gt;we tend to be biased toward Drupal as a solution to any problem (the &lt;a href=&quot;https://en.wikipedia.org/wiki/Law_of_the_instrument&quot;&gt;law of the instrument&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is true that Drupal is a great solution in many cases; and I love Drupal and the Drupal community.&lt;/p&gt;

&lt;p&gt;But we can only grow by getting off the Drupal island, and being open to objectively assess whether or not Drupal is right solution for a given use case and a given client.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“if you love something, set it free” —Unknown origin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;case-study-the-dcycle-blog&quot;&gt;Case study: the Dcycle blog&lt;/h2&gt;

&lt;p&gt;I have built my entire career on Drupal, and I have been accused (with reason) several times of being biased toward Drupal; in 2016 I am making a conscious effort to be open to other technologies and assess my commitment to Drupal more objectively.&lt;/p&gt;

&lt;p&gt;The result has been that I now tend to use Drupal for what it’s good at, data-heavy web applications with user-supplied content. However, I have integrated other technologies to my toolbox: among them &lt;a href=&quot;https://nodejs.org/en/&quot;&gt;node.js&lt;/a&gt; for real-time websocket communication, and &lt;a href=&quot;http://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt; for sites that don’t need to be dynamic on the server-side. In fact, these technologies can be used alongside Drupal to create a great ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://blog.dcycle.com&quot;&gt;My blog&lt;/a&gt; has looked like this for quite some time:&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;Very ugly design.&quot; src=&quot;http://blog.dcycle.com/assets/img/ugh.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It seemed to be time to refresh it. My goals were:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keeping the same paths and path aliases to all posts, for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/96/catching-watchdog-errors-your-simpletests&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/96&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node/96&lt;/code&gt; should all &lt;a href=&quot;http://blog.dcycle.com/blog/96/catching-watchdog-errors-your-simpletests&quot;&gt;redirect to the same page&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;Keep comment functionality;&lt;/li&gt;
  &lt;li&gt;Apply an open-source theme with minimal changes;&lt;/li&gt;
  &lt;li&gt;It should be easy for myself to add articles using the &lt;a href=&quot;https://guides.github.com/features/mastering-markdown/&quot;&gt;markdown syntax&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;There should be a contact form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My knee-jerk reaction would have been to build a Drupal 8 site, but looking at my requirements objectively, I realized that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Comments can easily be exported to &lt;a href=&quot;https://disqus.com&quot;&gt;Disqus&lt;/a&gt; using the &lt;a href=&quot;https://www.drupal.org/project/disqus_migrate&quot;&gt;Disqus Migrate&lt;/a&gt; module;&lt;/li&gt;
  &lt;li&gt;For my contact form I can use &lt;a href=&quot;https://formspree.io/&quot;&gt;formspree.io&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;Other than the above, there is no user-generated content;&lt;/li&gt;
  &lt;li&gt;Upgrading my blog between major versions every few years is a problem with Drupal;&lt;/li&gt;
  &lt;li&gt;Security updates and hosting require a lot of resources;&lt;/li&gt;
  &lt;li&gt;Backups of the database and files need to be tested every so often, which also requires resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I eventually settled on moving this blog away from Drupal toward &lt;a href=&quot;http://jekyllrb.com&quot;&gt;Jekyll&lt;/a&gt;, a static website generator which has the following advantages over Drupal for my use case:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What is actually publicly available is static HTML, ergo no security updates;&lt;/li&gt;
  &lt;li&gt;Because of its simplicity, testing backups is super easy;&lt;/li&gt;
  &lt;li&gt;My site can be hosted on GitHub using GitHub pages for free (&lt;del&gt;although HTTPS is not supported yet for custom domain names&lt;/del&gt; Github pages now supports secure HTTPS via Let’s encrypt);&lt;/li&gt;
  &lt;li&gt;All content and structure is stored in my git repo, so adding a blog post is as simple as adding a file to my git repo;&lt;/li&gt;
  &lt;li&gt;No PHP, no MySQL, just plain HTML and CSS: my blog now feels lightning fast;&lt;/li&gt;
  &lt;li&gt;Existing free and open-source templates are more plentiful for Jekyll than for Drupal, and if I can’t find what I want, it is easier to convert an HTML template to Jekyll than it is to convert it to Drupal (for me anyway).&lt;/li&gt;
  &lt;li&gt;Jekyll offers plugins for all of my project’s needs, including the &lt;a href=&quot;https://github.com/jekyll/jekyll-redirect-from&quot;&gt;Jekyll Redirect Form&lt;/a&gt; gem to define several paths for a single piece of content, including a canonical URL (permalink).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a nutshell, Jekyll works by regenerating an entirely new static website every time a change is made to underlying structured data, and putting the result in a subdirectory called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt;. All content and layout is structured in the directory hierarchy, and no database is used.&lt;/p&gt;

&lt;h2 id=&quot;exporting-content-from-drupal-to-jekyll&quot;&gt;Exporting content from Drupal to Jekyll&lt;/h2&gt;

&lt;p&gt;Depending on the complexity of your content, this will likely be the longest part of your migration, and will necessitate some trial and error. For the technical details of my own migration, see my blog post &lt;a href=&quot;http://blog.dcycle.com/blog/2016-09-30/migrating-drupal-jekyll/&quot;&gt;Migrating content from Drupal to Jekyll&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-i-learned&quot;&gt;What I learned&lt;/h2&gt;

&lt;p&gt;I set out with the goal of performing the entire migration in less than a few days, and I managed to do so, all the while learning more about Jekyll. I decided to spend as little time as possible on the design, instead reusing brianmaierjr’s open-source &lt;a href=&quot;https://github.com/brianmaierjr/long-haul&quot;&gt;Long Haul Jekyll theme&lt;/a&gt;. I estimate that I have managed to perform the migration to Jekyll in about 1/5th the time it would have taken me to migrate to Drupal 8, and I’m saving on hosting and maintenance as well. Some of my clients are interested in this approach as well, and are willing to trade an administrative backend for a large reduction in risk and cost.&lt;/p&gt;

&lt;h2 id=&quot;so-how-do-users-enter-content&quot;&gt;So how do users enter content?&lt;/h2&gt;

&lt;p&gt;Being the only person who updates this blog, I am confortable adding my content (text and images) as files in Github, but most non-technical users will prefer a backend. A few notes on this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, I have noticed that even though it is possible for clients to modify their Drupal site, many actually do not;&lt;/li&gt;
  &lt;li&gt;Many editors consider the Drupal backend to be very user-unfriendly to begin with, and may be willing instead of it to accept the technical Github interface and a little training if it saves them development time.&lt;/li&gt;
  &lt;li&gt;I see a big future for Jekyll frontends such as &lt;a href=&quot;http://prose.io/&quot;&gt;Prose.io&lt;/a&gt; which provide a neat editing interface (including image insertion) for editors of Jekyll sites hosted on GitHub.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I am not advocating replacing your Drupal sites with Jekyll, but in some cases we may benefit as a community by adding tools other than the &lt;a href=&quot;https://en.wikipedia.org/wiki/Law_of_the_instrument&quot;&gt;proverbial hammer&lt;/a&gt; to our toolbox.&lt;/p&gt;

&lt;p&gt;Static site generators such as Jekyll are one example of this, and with the interconnected web, making use of Drupal for what it’s good at will be, in the long term, good for Drupal, our community, our clients, and ourselves as developers&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;Unless you work exclusively with Drupal developers, you might be hearing some criticism of the Drupal community, among them:&lt;/p&gt;

</description>
        
        <pubDate>Sun, 02 Oct 2016 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/2016-10-02/when-not-to-use-drupal/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/2016-10-02/when-not-to-use-drupal/</guid>
        
        <category>planet</category>
        
        <category>blog</category>
        
        
      </item>
      
    
      
      <item>
        <title>Using Docker to evaluate, patch or develop Drupal modules</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://www.docker.com&quot;&gt;Docker&lt;/a&gt; is now available &lt;a href=&quot;https://docs.docker.com/docker-for-mac/&quot;&gt;natively on Mac OS&lt;/a&gt; in addition to Linux. Docker is also included with &lt;a href=&quot;https://coreos.com&quot;&gt;CoreOS&lt;/a&gt; which you can run on remote Virtual Machines, or locally through &lt;a href=&quot;https://coreos.com/os/docs/latest/booting-on-vagrant.html&quot;&gt;Vagrant&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have installed Docker and Git, locally or remotely, you don’t need to install anything else.&lt;/p&gt;

&lt;p&gt;In these examples we will leverage the official &lt;a href=&quot;https://hub.docker.com/_/drupal/&quot;&gt;Drupal&lt;/a&gt; and &lt;a href=&quot;https://hub.docker.com/_/mysql/&quot;&gt;mySQL&lt;/a&gt; Docker images. We will use the mySQL image as is, and we will add &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;Drush&lt;/a&gt; to our Drupal image.&lt;/p&gt;

&lt;p&gt;Docker is efficient with caching: these scripts will be slow the first time you run them, but very fast thereafter.&lt;/p&gt;

&lt;p&gt;Here are a few scripts I often use to set up quick Drupal 7 or 8 environments for module evaluation and development.&lt;/p&gt;

&lt;p&gt;Keep in mind that using Docker for deployment to production is another topic entirely and is not covered here; also, these scripts are meant to be &lt;em&gt;quick and dirty&lt;/em&gt;; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose&lt;/code&gt; might be useful for more advanced usage.&lt;/p&gt;

&lt;h2 id=&quot;port-mapping&quot;&gt;Port mapping&lt;/h2&gt;

&lt;p&gt;In all cases, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p 80&lt;/code&gt;, I map port 80 of Drupal to any port that happens to be available on my host, and in these examples I am using Docker for Mac OS, so my sites are available on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DRUPALPORT=$(docker ps|grep drupal7-container|sed &apos;s/.*0.0.0.0://g&apos;|sed &apos;s/-&amp;gt;.*//g&apos;)&lt;/code&gt; to figure out the current port of my running containers. When your containers are running, you can also just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker ps&lt;/code&gt; to see port mapping:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
f1bf6e7e51c9        drupal8-image       &quot;apache2-foreground&quot;     15 seconds ago      Up 11 seconds       0.0.0.0:32771-&amp;gt;80/tcp   drupal8-container
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example (scroll right to see more outpu), port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:32771&lt;/code&gt; will show your Drupal 8 site.&lt;/p&gt;

&lt;h2 id=&quot;using-docker-to-evaluate-patch-or-develop-drupal-7-modules&quot;&gt;Using Docker to evaluate, patch or develop Drupal 7 modules&lt;/h2&gt;

&lt;p&gt;I can set up a quick environment to evaluate one or more Drupal 7 modules. In this example I’ll evaluate Views.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir ~/drupal7-modules-to-evaluate
cd ~/drupal7-modules-to-evaluate
git clone --branch 7.x-3.x https://git.drupal.org/project/views.git
# add any other modules for evaluation here.

echo &apos;FROM drupal:7&apos; &amp;gt; Dockerfile
echo &apos;RUN curl -sS https://getcomposer.org/installer | php&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN mv composer.phar /usr/local/bin/composer&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN composer global require drush/drush:8&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN ln -s /root/.composer/vendor/drush/drush/drush /bin/drush&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN apt-get update &amp;amp;&amp;amp; apt-get upgrade -y&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN apt-get install -y mysql-client&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;EXPOSE 80&apos; &amp;gt;&amp;gt; Dockerfile

docker build -t drupal7-image .
docker run --name d7-mysql-container -e MYSQL_ROOT_PASSWORD=root -d mysql
docker run -v $(pwd):/var/www/html/sites/all/modules --name drupal7-container -p 80 --link d7-mysql-container:mysql -d drupal-image

DRUPALPORT=$(docker ps|grep drupal7-container|sed &apos;s/.*0.0.0.0://g&apos;|sed &apos;s/-&amp;gt;.*//g&apos;)

# wait for mysql to fire up. There&apos;s probably a better way of doing this...
# See stackoverflow.com/questions/21183088
# See https://github.com/docker/compose/issues/374
sleep 15

docker exec drupal7-container /bin/bash -c &quot;echo &apos;create database drupal&apos;|mysql -uroot -proot -hmysql&quot;
docker exec drupal7-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush si -y --db-url=mysql://root:root@mysql/drupal&quot;
docker exec drupal7-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush en views_ui -y&quot;
# enable any other modules here. Dependencies will be downloaded
# automatically

echo -e &quot;Your site is ready, you can log in with the link below&quot;

docker exec drupal7-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush uli -l http://localhost:$DRUPALPORT&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that we are &lt;em&gt;linking&lt;/em&gt; (rather than &lt;em&gt;adding&lt;/em&gt;) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/all/modules&lt;/code&gt; as a volume, so any change we make to our local copy of views will quasi-immediately be reflected on the container, making this a good technique to develop modules or write patches to existing modules.&lt;/p&gt;

&lt;p&gt;When you are finished you can destroy your containers, noting that all data will be lost:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker kill drupal7-container d7-mysql-container
docker rm drupal7-container d7-mysql-container
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;using-docker-to-evaluate-patch-or-develop-drupal-8-modules&quot;&gt;Using Docker to evaluate, patch or develop Drupal 8 modules&lt;/h2&gt;

&lt;p&gt;Our script for Drupal 8 modules is slightly different:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./modules&lt;/code&gt; is used on the container instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./sites/all/modules&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; is based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupal:8&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupal:7&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Unlike with Drupal 7, your database is not required to exist prior to installing Drupal with Drush;&lt;/li&gt;
  &lt;li&gt;In my tests I need to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chown /var/www/html/sites/default/files&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data:www-data&lt;/code&gt; to enable Drupal to write files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example where we are evaluating the &lt;a href=&quot;https://www.drupal.org/project/token&quot;&gt;Token&lt;/a&gt; module for Drupal 8:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir ~/drupal8-modules-to-evaluate
cd ~/drupal8-modules-to-evaluate
git clone --branch 8.x-1.x https://git.drupal.org/project/token.git
# add any other modules for evaluation here.

echo &apos;FROM drupal:8&apos; &amp;gt; Dockerfile
echo &apos;RUN curl -sS https://getcomposer.org/installer | php&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN mv composer.phar /usr/local/bin/composer&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN composer global require drush/drush:8&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN ln -s /root/.composer/vendor/drush/drush/drush /bin/drush&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN apt-get update &amp;amp;&amp;amp; apt-get upgrade -y&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN apt-get install -y mysql-client&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;EXPOSE 80&apos; &amp;gt;&amp;gt; Dockerfile

docker build -t drupal8-image .
docker run --name d8-mysql-container -e MYSQL_ROOT_PASSWORD=root -d mysql
docker run -v $(pwd):/var/www/html/modules --name drupal8-container -p 80 --link d8-mysql-container:mysql -d drupal8-image

DRUPALPORT=$(docker ps|grep drupal8-container|sed &apos;s/.*0.0.0.0://g&apos;|sed &apos;s/-&amp;gt;.*//g&apos;)

# wait for mysql to fire up. There&apos;s probably a better way of doing this...
# See stackoverflow.com/questions/21183088
# See https://github.com/docker/compose/issues/374
sleep 15

docker exec drupal8-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush si -y --db-url=mysql://root:root@mysql/drupal&quot;
docker exec drupal8-container /bin/bash -c &quot;chown -R www-data:www-data /var/www/html/sites/default/files&quot;
docker exec drupal8-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush en token -y&quot;
# enable any other modules here.

echo -e &quot;Your site is ready, you can log in with the link below&quot;

docker exec drupal8-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush uli -l http://localhost:$DRUPALPORT&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Again, when you are finished you can destroy your containers, noting that all data will be lost:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker kill drupal8-container d8-mysql-container
docker rm drupal8-container d8-mysql-container
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        
          <description>&lt;p&gt;&lt;a href=&quot;https://www.docker.com&quot;&gt;Docker&lt;/a&gt; is now available &lt;a href=&quot;https://docs.docker.com/docker-for-mac/&quot;&gt;natively on Mac OS&lt;/a&gt; in addition to Linux. Docker is also included with &lt;a href=&quot;https://coreos.com&quot;&gt;CoreOS&lt;/a&gt; which you can run on remote Virtual Machines, or locally through &lt;a href=&quot;https://coreos.com/os/docs/latest/booting-on-vagrant.html&quot;&gt;Vagrant&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have installed Docker and Git, locally or remotely, you don’t need to install anything else.&lt;/p&gt;

&lt;p&gt;In these examples we will leverage the official &lt;a href=&quot;https://hub.docker.com/_/drupal/&quot;&gt;Drupal&lt;/a&gt; and &lt;a href=&quot;https://hub.docker.com/_/mysql/&quot;&gt;mySQL&lt;/a&gt; Docker images. We will use the mySQL image as is, and we will add &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;Drush&lt;/a&gt; to our Drupal image.&lt;/p&gt;

&lt;p&gt;Docker is efficient with caching: these scripts will be slow the first time you run them, but very fast thereafter.&lt;/p&gt;

&lt;p&gt;Here are a few scripts I often use to set up quick Drupal 7 or 8 environments for module evaluation and development.&lt;/p&gt;

&lt;p&gt;Keep in mind that using Docker for deployment to production is another topic entirely and is not covered here; also, these scripts are meant to be &lt;em&gt;quick and dirty&lt;/em&gt;; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose&lt;/code&gt; might be useful for more advanced usage.&lt;/p&gt;

&lt;h2 id=&quot;port-mapping&quot;&gt;Port mapping&lt;/h2&gt;

&lt;p&gt;In all cases, using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-p 80&lt;/code&gt;, I map port 80 of Drupal to any port that happens to be available on my host, and in these examples I am using Docker for Mac OS, so my sites are available on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DRUPALPORT=$(docker ps|grep drupal7-container|sed &apos;s/.*0.0.0.0://g&apos;|sed &apos;s/-&amp;gt;.*//g&apos;)&lt;/code&gt; to figure out the current port of my running containers. When your containers are running, you can also just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker ps&lt;/code&gt; to see port mapping:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
f1bf6e7e51c9        drupal8-image       &quot;apache2-foreground&quot;     15 seconds ago      Up 11 seconds       0.0.0.0:32771-&amp;gt;80/tcp   drupal8-container
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example (scroll right to see more outpu), port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:32771&lt;/code&gt; will show your Drupal 8 site.&lt;/p&gt;

&lt;h2 id=&quot;using-docker-to-evaluate-patch-or-develop-drupal-7-modules&quot;&gt;Using Docker to evaluate, patch or develop Drupal 7 modules&lt;/h2&gt;

&lt;p&gt;I can set up a quick environment to evaluate one or more Drupal 7 modules. In this example I’ll evaluate Views.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir ~/drupal7-modules-to-evaluate
cd ~/drupal7-modules-to-evaluate
git clone --branch 7.x-3.x https://git.drupal.org/project/views.git
# add any other modules for evaluation here.

echo &apos;FROM drupal:7&apos; &amp;gt; Dockerfile
echo &apos;RUN curl -sS https://getcomposer.org/installer | php&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN mv composer.phar /usr/local/bin/composer&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN composer global require drush/drush:8&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN ln -s /root/.composer/vendor/drush/drush/drush /bin/drush&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN apt-get update &amp;amp;&amp;amp; apt-get upgrade -y&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN apt-get install -y mysql-client&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;EXPOSE 80&apos; &amp;gt;&amp;gt; Dockerfile

docker build -t drupal7-image .
docker run --name d7-mysql-container -e MYSQL_ROOT_PASSWORD=root -d mysql
docker run -v $(pwd):/var/www/html/sites/all/modules --name drupal7-container -p 80 --link d7-mysql-container:mysql -d drupal-image

DRUPALPORT=$(docker ps|grep drupal7-container|sed &apos;s/.*0.0.0.0://g&apos;|sed &apos;s/-&amp;gt;.*//g&apos;)

# wait for mysql to fire up. There&apos;s probably a better way of doing this...
# See stackoverflow.com/questions/21183088
# See https://github.com/docker/compose/issues/374
sleep 15

docker exec drupal7-container /bin/bash -c &quot;echo &apos;create database drupal&apos;|mysql -uroot -proot -hmysql&quot;
docker exec drupal7-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush si -y --db-url=mysql://root:root@mysql/drupal&quot;
docker exec drupal7-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush en views_ui -y&quot;
# enable any other modules here. Dependencies will be downloaded
# automatically

echo -e &quot;Your site is ready, you can log in with the link below&quot;

docker exec drupal7-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush uli -l http://localhost:$DRUPALPORT&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that we are &lt;em&gt;linking&lt;/em&gt; (rather than &lt;em&gt;adding&lt;/em&gt;) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/all/modules&lt;/code&gt; as a volume, so any change we make to our local copy of views will quasi-immediately be reflected on the container, making this a good technique to develop modules or write patches to existing modules.&lt;/p&gt;

&lt;p&gt;When you are finished you can destroy your containers, noting that all data will be lost:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker kill drupal7-container d7-mysql-container
docker rm drupal7-container d7-mysql-container
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;using-docker-to-evaluate-patch-or-develop-drupal-8-modules&quot;&gt;Using Docker to evaluate, patch or develop Drupal 8 modules&lt;/h2&gt;

&lt;p&gt;Our script for Drupal 8 modules is slightly different:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./modules&lt;/code&gt; is used on the container instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./sites/all/modules&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; is based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupal:8&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drupal:7&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;Unlike with Drupal 7, your database is not required to exist prior to installing Drupal with Drush;&lt;/li&gt;
  &lt;li&gt;In my tests I need to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chown /var/www/html/sites/default/files&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;www-data:www-data&lt;/code&gt; to enable Drupal to write files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example where we are evaluating the &lt;a href=&quot;https://www.drupal.org/project/token&quot;&gt;Token&lt;/a&gt; module for Drupal 8:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir ~/drupal8-modules-to-evaluate
cd ~/drupal8-modules-to-evaluate
git clone --branch 8.x-1.x https://git.drupal.org/project/token.git
# add any other modules for evaluation here.

echo &apos;FROM drupal:8&apos; &amp;gt; Dockerfile
echo &apos;RUN curl -sS https://getcomposer.org/installer | php&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN mv composer.phar /usr/local/bin/composer&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN composer global require drush/drush:8&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN ln -s /root/.composer/vendor/drush/drush/drush /bin/drush&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN apt-get update &amp;amp;&amp;amp; apt-get upgrade -y&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;RUN apt-get install -y mysql-client&apos; &amp;gt;&amp;gt; Dockerfile
echo &apos;EXPOSE 80&apos; &amp;gt;&amp;gt; Dockerfile

docker build -t drupal8-image .
docker run --name d8-mysql-container -e MYSQL_ROOT_PASSWORD=root -d mysql
docker run -v $(pwd):/var/www/html/modules --name drupal8-container -p 80 --link d8-mysql-container:mysql -d drupal8-image

DRUPALPORT=$(docker ps|grep drupal8-container|sed &apos;s/.*0.0.0.0://g&apos;|sed &apos;s/-&amp;gt;.*//g&apos;)

# wait for mysql to fire up. There&apos;s probably a better way of doing this...
# See stackoverflow.com/questions/21183088
# See https://github.com/docker/compose/issues/374
sleep 15

docker exec drupal8-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush si -y --db-url=mysql://root:root@mysql/drupal&quot;
docker exec drupal8-container /bin/bash -c &quot;chown -R www-data:www-data /var/www/html/sites/default/files&quot;
docker exec drupal8-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush en token -y&quot;
# enable any other modules here.

echo -e &quot;Your site is ready, you can log in with the link below&quot;

docker exec drupal8-container /bin/bash -c &quot;cd /var/www/html &amp;amp;&amp;amp; drush uli -l http://localhost:$DRUPALPORT&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Again, when you are finished you can destroy your containers, noting that all data will be lost:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker kill drupal8-container d8-mysql-container
docker rm drupal8-container d8-mysql-container
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        
        <pubDate>Mon, 19 Sep 2016 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/113/using-docker-evaluate-patch-or-develop-drupal-modules/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/113/using-docker-evaluate-patch-or-develop-drupal-modules/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Catching watchdog errors in your Simpletests</title>
        <description>&lt;p&gt;If you are using a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;, and running simpletests against it in your continuous integration server using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush test-run&lt;/code&gt;, you might come across Simpletest output like this in your Jenkins console output:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Starting test MyModuleTestCase.                                         [ok]
...
WD rules: Unable to get variable some_variable, it is not           [error]
defined.
...
MyModuleTestCase 9 passes, 0 fails, 0 exceptions, and 7 debug messages  [ok]
No leftover tables to remove.                                           [status]
No temporary directories to remove.                                     [status]
Removed 1 test result.                                                  [status]
 Group  Class  Name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, the Rules module is complaining that it is misconfigured. You will probably be able to confirm this by installing a local version of your site along with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rules_ui&lt;/code&gt; and visiting the rules admin page.&lt;/p&gt;

&lt;p&gt;Here, it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rules&lt;/code&gt; which is logging a watchdog error, but it could by any module.&lt;/p&gt;

&lt;p&gt;However, this will not necessarily cause your test to fail (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0 fails&lt;/code&gt;), and more importantly, your continuous integration script will not fail either.&lt;/p&gt;

&lt;p&gt;At first you might find it strange that your console output shows &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[error]&lt;/code&gt;, but that your script is still passing. You script probably looks something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set -e
drush test-run MyModuleTestCase
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush test-run&lt;/code&gt; outputs an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[error]&lt;/code&gt; message, but is still exiting with the normal exit code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;. How can that be?&lt;/p&gt;

&lt;p&gt;Well, your test is doing exactly what you are asking of it: it is asserting that certain conditions are met, but you have never explicitly asked it to fail when a watchdog error is logged within the temporary testing environment. This is normal: consider a case where you want to assert that a given piece of code logs an error. In your test, you will create the necessary conditions for the error to be logged, and then you will assert that the error has in fact been logged. In this case your test will fail if the error has not been logged, but will succeed if the error has been logged. This is why the test script should not fail every time there is an error.&lt;/p&gt;

&lt;p&gt;But in our above example, we have no way of knowing when such an error is introduced; to ensure more robust testing, let’s add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;teardown&lt;/code&gt; function to our test which asserts that no errors were logged during any of our tests. To make sure that the tests don’t fail when errors are expected, we will allow for that as well.&lt;/p&gt;

&lt;p&gt;Add the following code to your Simpletest (if you have several tests, consider creating a base test for all of them to avoid reusing code):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * {inheritdoc}
 */
function tearDown() {
  // See http://blog.dcycle.com/blog/96/catching-watchdog-errors-your-simpletests
  $num_errors = $this-&amp;gt;getNumWatchdogEntries(WATCHDOG_ERROR);
  $expected_errors = isset($this-&amp;gt;expected_errors) ? $this-&amp;gt;expected_errors : 0;
  $this-&amp;gt;assertTrue($num_errors == $expected_errors, &apos;Expected &apos; . $expected_errors . &apos; watchdog errors and got &apos; . $num_errors . &apos;.&apos;);

  parent::tearDown();
}

/**
 * Get the number of watchdog entries for a given severity or worse
 *
 * See http://blog.dcycle.com/blog/96/catching-watchdog-errors-your-simpletests
 *
 * @param $severity = WATCHDOG_ERROR
 *   Severity codes are listed at https://api.drupal.org/api/drupal/includes%21bootstrap.inc/group/logging_severity_levels/7
 *   Lower numbers are worse severity messages, for example an emergency is 0, and an
 *   error is 3.
 *   Specify a threshold here, for example for the default WATCHDOG_ERROR, this function
 *   will return the number of watchdog entries which are 0, 1, 2, or 3.
 *
 * @return
 *   The number of watchdog errors logged during this test.
 */
function getNumWatchdogEntries($severity = WATCHDOG_ERROR) {
  $results = db_select(&apos;watchdog&apos;)
      -&amp;gt;fields(NULL, array(&apos;wid&apos;))
      -&amp;gt;condition(&apos;severity&apos;, $severity, &apos;&amp;lt;=&apos;)
      -&amp;gt;execute()
      -&amp;gt;fetchAll();
  return count($results);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, all your tests which have this code will fail if there are any watchdog errors in it. If you are actually expecting there to be errors, then at some point in your test you could use this code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$this-&amp;gt;expected_errors = 1; // for example
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        
          <description>&lt;p&gt;If you are using a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;, and running simpletests against it in your continuous integration server using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush test-run&lt;/code&gt;, you might come across Simpletest output like this in your Jenkins console output:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Starting test MyModuleTestCase.                                         [ok]
...
WD rules: Unable to get variable some_variable, it is not           [error]
defined.
...
MyModuleTestCase 9 passes, 0 fails, 0 exceptions, and 7 debug messages  [ok]
No leftover tables to remove.                                           [status]
No temporary directories to remove.                                     [status]
Removed 1 test result.                                                  [status]
 Group  Class  Name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, the Rules module is complaining that it is misconfigured. You will probably be able to confirm this by installing a local version of your site along with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rules_ui&lt;/code&gt; and visiting the rules admin page.&lt;/p&gt;

&lt;p&gt;Here, it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rules&lt;/code&gt; which is logging a watchdog error, but it could by any module.&lt;/p&gt;

&lt;p&gt;However, this will not necessarily cause your test to fail (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0 fails&lt;/code&gt;), and more importantly, your continuous integration script will not fail either.&lt;/p&gt;

&lt;p&gt;At first you might find it strange that your console output shows &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[error]&lt;/code&gt;, but that your script is still passing. You script probably looks something like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set -e
drush test-run MyModuleTestCase
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush test-run&lt;/code&gt; outputs an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[error]&lt;/code&gt; message, but is still exiting with the normal exit code of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt;. How can that be?&lt;/p&gt;

&lt;p&gt;Well, your test is doing exactly what you are asking of it: it is asserting that certain conditions are met, but you have never explicitly asked it to fail when a watchdog error is logged within the temporary testing environment. This is normal: consider a case where you want to assert that a given piece of code logs an error. In your test, you will create the necessary conditions for the error to be logged, and then you will assert that the error has in fact been logged. In this case your test will fail if the error has not been logged, but will succeed if the error has been logged. This is why the test script should not fail every time there is an error.&lt;/p&gt;

&lt;p&gt;But in our above example, we have no way of knowing when such an error is introduced; to ensure more robust testing, let’s add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;teardown&lt;/code&gt; function to our test which asserts that no errors were logged during any of our tests. To make sure that the tests don’t fail when errors are expected, we will allow for that as well.&lt;/p&gt;

&lt;p&gt;Add the following code to your Simpletest (if you have several tests, consider creating a base test for all of them to avoid reusing code):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * {inheritdoc}
 */
function tearDown() {
  // See http://blog.dcycle.com/blog/96/catching-watchdog-errors-your-simpletests
  $num_errors = $this-&amp;gt;getNumWatchdogEntries(WATCHDOG_ERROR);
  $expected_errors = isset($this-&amp;gt;expected_errors) ? $this-&amp;gt;expected_errors : 0;
  $this-&amp;gt;assertTrue($num_errors == $expected_errors, &apos;Expected &apos; . $expected_errors . &apos; watchdog errors and got &apos; . $num_errors . &apos;.&apos;);

  parent::tearDown();
}

/**
 * Get the number of watchdog entries for a given severity or worse
 *
 * See http://blog.dcycle.com/blog/96/catching-watchdog-errors-your-simpletests
 *
 * @param $severity = WATCHDOG_ERROR
 *   Severity codes are listed at https://api.drupal.org/api/drupal/includes%21bootstrap.inc/group/logging_severity_levels/7
 *   Lower numbers are worse severity messages, for example an emergency is 0, and an
 *   error is 3.
 *   Specify a threshold here, for example for the default WATCHDOG_ERROR, this function
 *   will return the number of watchdog entries which are 0, 1, 2, or 3.
 *
 * @return
 *   The number of watchdog errors logged during this test.
 */
function getNumWatchdogEntries($severity = WATCHDOG_ERROR) {
  $results = db_select(&apos;watchdog&apos;)
      -&amp;gt;fields(NULL, array(&apos;wid&apos;))
      -&amp;gt;condition(&apos;severity&apos;, $severity, &apos;&amp;lt;=&apos;)
      -&amp;gt;execute()
      -&amp;gt;fetchAll();
  return count($results);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, all your tests which have this code will fail if there are any watchdog errors in it. If you are actually expecting there to be errors, then at some point in your test you could use this code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$this-&amp;gt;expected_errors = 1; // for example
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        
        <pubDate>Mon, 06 Jul 2015 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/96/catching-watchdog-errors-your-simpletests/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/96/catching-watchdog-errors-your-simpletests/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Add unit testing to legacy code</title>
        <description>&lt;p&gt;&lt;strong&gt;Edit, this blog post is deprecated, see &lt;a href=&quot;https://blog.dcycle.com/unit&quot;&gt;blog.dcycle.com/unit&lt;/a&gt; instead!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To me, modern code must be tracked by a continuous integration server, and must have automated tests. Anything else is legacy code, even if it was rolled out this morning.&lt;/p&gt;

&lt;p&gt;In the last year, I have adopted a policy of never modifying any legacy code, because even a one-line change can have unanticipated effects on functionality, plus there is no guarantee that you won’t be re-fixing the same problem in 6 months.&lt;/p&gt;

&lt;p&gt;This article will focus on a simple technique I use to bring legacy Drupal code under a test harness (hence transforming it into modern code), which is my first step before working on it.&lt;/p&gt;

&lt;h2 id=&quot;unit-vs-functional-testing&quot;&gt;Unit vs. functional testing&lt;/h2&gt;

&lt;p&gt;If you have already written automated tests for Drupal, you know about Simpletest and the concept of functional web-request tests with a temporary database: the vast majority of tests written for Drupal 7 code are based on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DrupalWebTestCase&lt;/code&gt;, which builds a Drupal site from scratch, often installing something like a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;, using a temporary database, and then allows your test to make web requests to that interface. It’s all automatic and temporary environments are destroyed when tests are done.&lt;/p&gt;

&lt;p&gt;It’s great, it really simulates how your site is used, but it has some drawbacks: first, it’s a bit of a pain to set up: your continuous integration server needs to have a LAMP stack or spin up Vagrant boxes or Docker containers, you need to set up virtual hosts for your code, and most importantly, it’s very time-consuming, because each test case in each test class creates a brand new Drupal site, installs your modules, and destroys the environment.&lt;/p&gt;

&lt;p&gt;(I even had to &lt;a href=&quot;https://www.drupal.org/project/simpletest_turbo&quot;&gt;write a module, Simpletest Turbo&lt;/a&gt;, to perform some caching, or else my tests were taking hours to run (at which point everyone starts ignoring them) – but that is just a stopgap measure.)&lt;/p&gt;

&lt;p&gt;Unit tests, on the other hand, don’t require a database, don’t do web requests, and are lightning fast, often running in less than a second.&lt;/p&gt;

&lt;p&gt;This article will detail how I use unit testing on legacy code.&lt;/p&gt;

&lt;h2 id=&quot;typical-legacy-code&quot;&gt;Typical legacy code&lt;/h2&gt;

&lt;p&gt;Typically, you will be asked to make a “small change” to a function which is often 200+ lines long, and uses global variables, performs database requests, and REST calls to external services. But I’m not judging the authors of such code – more often than not, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git blame&lt;/code&gt; tells me that I wrote it myself.&lt;/p&gt;

&lt;p&gt;For the purposes of our example, let’s imagine that you are asked to make change to a function which returns a “score” for the current user.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function mymodule_user_score() {
  global $user;
  $user = user_load($user-&amp;gt;uid);
  $node = node_load($user-&amp;gt;field_score_nid[&apos;und&apos;][0][&apos;value&apos;]);
  return $node-&amp;gt;field_score[&apos;und&apos;][0][&apos;value&apos;];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This example is not too menacing, but it’s still not unit testable: the function calls the database, and uses global variables.&lt;/p&gt;

&lt;p&gt;Now, the above function is not very elegant; our first task is to ignore our impulse to improve it. Remember: we’re not going to even touch any code that’s not under a test harness.&lt;/p&gt;

&lt;p&gt;As mentioned above, we could write a subclass of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DrupalWebTestCase&lt;/code&gt; which provisions a database, we could create a node, a user, populate it, and then run the function.&lt;/p&gt;

&lt;p&gt;But we would rather write a unit test, which does not need externalities like the database or global variables.&lt;/p&gt;

&lt;p&gt;But our function &lt;em&gt;depends&lt;/em&gt; on externalities! How can we ignore them? We’ll use a technique called &lt;em&gt;dependency injection&lt;/em&gt;. There are several approaches to dependency injection; and Drupal 8 code supports it very well with PHPUnit; but we’ll use a simple implementation which requires the following steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Move the code to a class method&lt;/li&gt;
  &lt;li&gt;Move dependencies into their own methods&lt;/li&gt;
  &lt;li&gt;Write a subclass replaces dependencies (not logic) with mock implementations&lt;/li&gt;
  &lt;li&gt;Write a test&lt;/li&gt;
  &lt;li&gt;Then, &lt;em&gt;and only then&lt;/em&gt;, make the “small change” requested by the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2 id=&quot;move-the-code-to-a-class-method&quot;&gt;Move the code to a class method&lt;/h2&gt;

&lt;p&gt;For dependency to work, we need to put the above code in a class, so our code will now look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MyModuleUserScore {
  function mymodule_user_score() {
    global $user;
    $user = user_load($user-&amp;gt;uid);
    $node = node_load($user-&amp;gt;field_score_nid[&apos;und&apos;][0][&apos;value&apos;]);
    return $node-&amp;gt;field_score[&apos;und&apos;][0][&apos;value&apos;];
  }
}

function mymodule_user_score() {
  $score = new MyModuleUserScore();
  return $score-&amp;gt;mymodule_user_score();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That wasn’t that hard, right? I like to keep each of my classes in its own file, but for simplicity’s sake let’s assume everything is in the same file.&lt;/p&gt;

&lt;h2 id=&quot;move-dependencies-into-their-own-methods&quot;&gt;Move dependencies into their own methods&lt;/h2&gt;

&lt;p&gt;There are a few dependencies in this function: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;global $user&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_load()&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_load()&lt;/code&gt;. All of these are not available to unit tests, so we need to move them out of the function, like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MyModuleUserScore {
  function mymodule_user_score() {
    $user = $this-&amp;gt;globalUser();
    $user = $this-&amp;gt;user_load($user-&amp;gt;uid);
    $node = $this-&amp;gt;node_load($user-&amp;gt;field_score_nid[&apos;und&apos;][0][&apos;value&apos;]);
    return $node-&amp;gt;field_score[&apos;und&apos;][0][&apos;value&apos;];
  }

  function globalUser() {
    return global $user;
  }

  function user_load($uid) {
    return user_load($uid);
  }

  function node_load($nid) {
    return node_load($nid);
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your dependency methods should generally only contain one line. The above code should behave in exactly the same way as the original.&lt;/p&gt;

&lt;h2 id=&quot;override-dependencies-in-a-subclass&quot;&gt;Override dependencies in a subclass&lt;/h2&gt;

&lt;p&gt;Our next step will be to provide mock versions of our dependencies. The trick here is to make our mock versions return values which are expected by the main function. For example, we can surmise that our user is expected to have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;field_score_nid&lt;/code&gt;, which is expected to contain a valid node id. We can also make similar assumptions about how our node is structured. Let’s make mock responses with these assumptions:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MyModuleUserScoreMock extends MyModuleUserScore {
  function globalUser() {
    return (object) array(
      &apos;uid&apos; =&amp;gt; 123,
    );
  }

  function user_load($uid) {
    if ($uid == 123) {
      return (object) array {
        field_score_nid =&amp;gt; array(
          LANGUAGE_NONE =&amp;gt; array(
            array(
              &apos;value&apos; =&amp;gt; 234,
            ),
          ),
        ),
      }
    }
  }

  function node_load($nid) {
    if ($nid == 234) {
      return (object) array {
        field_score =&amp;gt; array(
          LANGUAGE_NONE =&amp;gt; array(
            array(
              &apos;value&apos; =&amp;gt; 3000,
            ),
          ),
        ),
      }
    }
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that our return values are not meant to be complete: they only contain the minimal data expected by our function: our mock user object does not even contain a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uid&lt;/code&gt; property! But that does not matter, because our function is not expecting it.&lt;/p&gt;

&lt;h2 id=&quot;write-a-test&quot;&gt;Write a test&lt;/h2&gt;

&lt;p&gt;It is now possible to write a unit test for our logic without requiring the database. You can copy the contents of &lt;a href=&quot;http://blog.dcycle.com/blog/basic-unit-test&quot;&gt;this sample unit test&lt;/a&gt; to your module folder as mymodule.test, add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;files[] = mymodule.test&lt;/code&gt; to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mymodule.info&lt;/code&gt;, enable the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simpletest&lt;/code&gt; modules and clear your cache.&lt;/p&gt;

&lt;p&gt;There remains the task of actually writing the test: in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testModule()&lt;/code&gt; function, the following lines will do:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public function testModule() {
  // load the file or files where your classes are located. This can
  // also be done in the setUp() function.
  module_load_include(&apos;module&apos;, &apos;mymodule&apos;);

  $score = new MyModuleUserScoreMock();
  $this-&amp;gt;assertTrue($score-&amp;gt;mymodule_user_score() == 3000, &apos;User score function returns the expected score&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;run-your-test&quot;&gt;Run your test&lt;/h2&gt;

&lt;p&gt;All that’s left now is to run your test:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;php ./scripts/run-tests.sh --class mymoduleTestCase
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then add above line to your continuous integration server to make sure you’re notified when someone breaks it.&lt;/p&gt;

&lt;h2 id=&quot;your-code-is-now-ready-to-be-fixed&quot;&gt;Your code is now ready to be fixed&lt;/h2&gt;

&lt;p&gt;Now, when your client asks for a small or big change, you can use test-driven development to implement it. For example, let’s say your client wants all scores to be multiplied by 10 (30000 should be the score when 3000 is the value in the node):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, modify your unit test to make sure it fails: make the test expect 30000 instead of 3000&lt;/li&gt;
  &lt;li&gt;Next, change your code iteratively until your test passes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;

&lt;p&gt;This has been a very simple introduction to dependency injection and unit testing for legacy code: if you want to do even more, you can make your Mock subclass as complex as you wish, simulating corrupt data, nodes which don’t load, and so on.&lt;/p&gt;

&lt;p&gt;I highly recommend getting familiar with PHPUnit, which is part of Drupal 8, and which takes dependency injection to a whole new level: Juan Treminio’s &lt;a href=&quot;https://jtreminio.com/2013/03/unit-testing-tutorial-introduction-to-phpunit/&quot;&gt;“Unit Testing Tutorial Part I: Introduction to PHPUnit”, March 1, 2013&lt;/a&gt; is the best introduction I’ve found.&lt;/p&gt;

&lt;p&gt;I do not recommend doing away entirely with functional, database, and web tests, but a layered approach where most of your tests are unit tests, and you limit the use of functional tests, will allow you to keep your test runs below an acceptable duration, making them all the more useful, and increasing the overall quality of new and even legacy code.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;&lt;strong&gt;Edit, this blog post is deprecated, see &lt;a href=&quot;https://blog.dcycle.com/unit&quot;&gt;blog.dcycle.com/unit&lt;/a&gt; instead!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To me, modern code must be tracked by a continuous integration server, and must have automated tests. Anything else is legacy code, even if it was rolled out this morning.&lt;/p&gt;

&lt;p&gt;In the last year, I have adopted a policy of never modifying any legacy code, because even a one-line change can have unanticipated effects on functionality, plus there is no guarantee that you won’t be re-fixing the same problem in 6 months.&lt;/p&gt;

&lt;p&gt;This article will focus on a simple technique I use to bring legacy Drupal code under a test harness (hence transforming it into modern code), which is my first step before working on it.&lt;/p&gt;

&lt;h2 id=&quot;unit-vs-functional-testing&quot;&gt;Unit vs. functional testing&lt;/h2&gt;

&lt;p&gt;If you have already written automated tests for Drupal, you know about Simpletest and the concept of functional web-request tests with a temporary database: the vast majority of tests written for Drupal 7 code are based on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DrupalWebTestCase&lt;/code&gt;, which builds a Drupal site from scratch, often installing something like a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;, using a temporary database, and then allows your test to make web requests to that interface. It’s all automatic and temporary environments are destroyed when tests are done.&lt;/p&gt;

&lt;p&gt;It’s great, it really simulates how your site is used, but it has some drawbacks: first, it’s a bit of a pain to set up: your continuous integration server needs to have a LAMP stack or spin up Vagrant boxes or Docker containers, you need to set up virtual hosts for your code, and most importantly, it’s very time-consuming, because each test case in each test class creates a brand new Drupal site, installs your modules, and destroys the environment.&lt;/p&gt;

&lt;p&gt;(I even had to &lt;a href=&quot;https://www.drupal.org/project/simpletest_turbo&quot;&gt;write a module, Simpletest Turbo&lt;/a&gt;, to perform some caching, or else my tests were taking hours to run (at which point everyone starts ignoring them) – but that is just a stopgap measure.)&lt;/p&gt;

&lt;p&gt;Unit tests, on the other hand, don’t require a database, don’t do web requests, and are lightning fast, often running in less than a second.&lt;/p&gt;

&lt;p&gt;This article will detail how I use unit testing on legacy code.&lt;/p&gt;

&lt;h2 id=&quot;typical-legacy-code&quot;&gt;Typical legacy code&lt;/h2&gt;

&lt;p&gt;Typically, you will be asked to make a “small change” to a function which is often 200+ lines long, and uses global variables, performs database requests, and REST calls to external services. But I’m not judging the authors of such code – more often than not, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git blame&lt;/code&gt; tells me that I wrote it myself.&lt;/p&gt;

&lt;p&gt;For the purposes of our example, let’s imagine that you are asked to make change to a function which returns a “score” for the current user.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function mymodule_user_score() {
  global $user;
  $user = user_load($user-&amp;gt;uid);
  $node = node_load($user-&amp;gt;field_score_nid[&apos;und&apos;][0][&apos;value&apos;]);
  return $node-&amp;gt;field_score[&apos;und&apos;][0][&apos;value&apos;];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This example is not too menacing, but it’s still not unit testable: the function calls the database, and uses global variables.&lt;/p&gt;

&lt;p&gt;Now, the above function is not very elegant; our first task is to ignore our impulse to improve it. Remember: we’re not going to even touch any code that’s not under a test harness.&lt;/p&gt;

&lt;p&gt;As mentioned above, we could write a subclass of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DrupalWebTestCase&lt;/code&gt; which provisions a database, we could create a node, a user, populate it, and then run the function.&lt;/p&gt;

&lt;p&gt;But we would rather write a unit test, which does not need externalities like the database or global variables.&lt;/p&gt;

&lt;p&gt;But our function &lt;em&gt;depends&lt;/em&gt; on externalities! How can we ignore them? We’ll use a technique called &lt;em&gt;dependency injection&lt;/em&gt;. There are several approaches to dependency injection; and Drupal 8 code supports it very well with PHPUnit; but we’ll use a simple implementation which requires the following steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Move the code to a class method&lt;/li&gt;
  &lt;li&gt;Move dependencies into their own methods&lt;/li&gt;
  &lt;li&gt;Write a subclass replaces dependencies (not logic) with mock implementations&lt;/li&gt;
  &lt;li&gt;Write a test&lt;/li&gt;
  &lt;li&gt;Then, &lt;em&gt;and only then&lt;/em&gt;, make the “small change” requested by the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2 id=&quot;move-the-code-to-a-class-method&quot;&gt;Move the code to a class method&lt;/h2&gt;

&lt;p&gt;For dependency to work, we need to put the above code in a class, so our code will now look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MyModuleUserScore {
  function mymodule_user_score() {
    global $user;
    $user = user_load($user-&amp;gt;uid);
    $node = node_load($user-&amp;gt;field_score_nid[&apos;und&apos;][0][&apos;value&apos;]);
    return $node-&amp;gt;field_score[&apos;und&apos;][0][&apos;value&apos;];
  }
}

function mymodule_user_score() {
  $score = new MyModuleUserScore();
  return $score-&amp;gt;mymodule_user_score();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That wasn’t that hard, right? I like to keep each of my classes in its own file, but for simplicity’s sake let’s assume everything is in the same file.&lt;/p&gt;

&lt;h2 id=&quot;move-dependencies-into-their-own-methods&quot;&gt;Move dependencies into their own methods&lt;/h2&gt;

&lt;p&gt;There are a few dependencies in this function: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;global $user&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user_load()&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_load()&lt;/code&gt;. All of these are not available to unit tests, so we need to move them out of the function, like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MyModuleUserScore {
  function mymodule_user_score() {
    $user = $this-&amp;gt;globalUser();
    $user = $this-&amp;gt;user_load($user-&amp;gt;uid);
    $node = $this-&amp;gt;node_load($user-&amp;gt;field_score_nid[&apos;und&apos;][0][&apos;value&apos;]);
    return $node-&amp;gt;field_score[&apos;und&apos;][0][&apos;value&apos;];
  }

  function globalUser() {
    return global $user;
  }

  function user_load($uid) {
    return user_load($uid);
  }

  function node_load($nid) {
    return node_load($nid);
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your dependency methods should generally only contain one line. The above code should behave in exactly the same way as the original.&lt;/p&gt;

&lt;h2 id=&quot;override-dependencies-in-a-subclass&quot;&gt;Override dependencies in a subclass&lt;/h2&gt;

&lt;p&gt;Our next step will be to provide mock versions of our dependencies. The trick here is to make our mock versions return values which are expected by the main function. For example, we can surmise that our user is expected to have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;field_score_nid&lt;/code&gt;, which is expected to contain a valid node id. We can also make similar assumptions about how our node is structured. Let’s make mock responses with these assumptions:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class MyModuleUserScoreMock extends MyModuleUserScore {
  function globalUser() {
    return (object) array(
      &apos;uid&apos; =&amp;gt; 123,
    );
  }

  function user_load($uid) {
    if ($uid == 123) {
      return (object) array {
        field_score_nid =&amp;gt; array(
          LANGUAGE_NONE =&amp;gt; array(
            array(
              &apos;value&apos; =&amp;gt; 234,
            ),
          ),
        ),
      }
    }
  }

  function node_load($nid) {
    if ($nid == 234) {
      return (object) array {
        field_score =&amp;gt; array(
          LANGUAGE_NONE =&amp;gt; array(
            array(
              &apos;value&apos; =&amp;gt; 3000,
            ),
          ),
        ),
      }
    }
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that our return values are not meant to be complete: they only contain the minimal data expected by our function: our mock user object does not even contain a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uid&lt;/code&gt; property! But that does not matter, because our function is not expecting it.&lt;/p&gt;

&lt;h2 id=&quot;write-a-test&quot;&gt;Write a test&lt;/h2&gt;

&lt;p&gt;It is now possible to write a unit test for our logic without requiring the database. You can copy the contents of &lt;a href=&quot;http://blog.dcycle.com/blog/basic-unit-test&quot;&gt;this sample unit test&lt;/a&gt; to your module folder as mymodule.test, add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;files[] = mymodule.test&lt;/code&gt; to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mymodule.info&lt;/code&gt;, enable the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simpletest&lt;/code&gt; modules and clear your cache.&lt;/p&gt;

&lt;p&gt;There remains the task of actually writing the test: in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testModule()&lt;/code&gt; function, the following lines will do:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public function testModule() {
  // load the file or files where your classes are located. This can
  // also be done in the setUp() function.
  module_load_include(&apos;module&apos;, &apos;mymodule&apos;);

  $score = new MyModuleUserScoreMock();
  $this-&amp;gt;assertTrue($score-&amp;gt;mymodule_user_score() == 3000, &apos;User score function returns the expected score&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;run-your-test&quot;&gt;Run your test&lt;/h2&gt;

&lt;p&gt;All that’s left now is to run your test:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;php ./scripts/run-tests.sh --class mymoduleTestCase
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then add above line to your continuous integration server to make sure you’re notified when someone breaks it.&lt;/p&gt;

&lt;h2 id=&quot;your-code-is-now-ready-to-be-fixed&quot;&gt;Your code is now ready to be fixed&lt;/h2&gt;

&lt;p&gt;Now, when your client asks for a small or big change, you can use test-driven development to implement it. For example, let’s say your client wants all scores to be multiplied by 10 (30000 should be the score when 3000 is the value in the node):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, modify your unit test to make sure it fails: make the test expect 30000 instead of 3000&lt;/li&gt;
  &lt;li&gt;Next, change your code iteratively until your test passes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;

&lt;p&gt;This has been a very simple introduction to dependency injection and unit testing for legacy code: if you want to do even more, you can make your Mock subclass as complex as you wish, simulating corrupt data, nodes which don’t load, and so on.&lt;/p&gt;

&lt;p&gt;I highly recommend getting familiar with PHPUnit, which is part of Drupal 8, and which takes dependency injection to a whole new level: Juan Treminio’s &lt;a href=&quot;https://jtreminio.com/2013/03/unit-testing-tutorial-introduction-to-phpunit/&quot;&gt;“Unit Testing Tutorial Part I: Introduction to PHPUnit”, March 1, 2013&lt;/a&gt; is the best introduction I’ve found.&lt;/p&gt;

&lt;p&gt;I do not recommend doing away entirely with functional, database, and web tests, but a layered approach where most of your tests are unit tests, and you limit the use of functional tests, will allow you to keep your test runs below an acceptable duration, making them all the more useful, and increasing the overall quality of new and even legacy code.&lt;/p&gt;
</description>
        
        <pubDate>Wed, 10 Jun 2015 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/94/add-unit-testing-legacy-code/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/94/add-unit-testing-legacy-code/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Continuous integration with Circle CI and Docker for your Drupal project</title>
        <description>&lt;p&gt;Continuous integration (CI) is the practice of running a series of checks on every push of your code, to make sure it is always in a potentially deployable state; and to make sure you are alerted as soon as possible if it is not.&lt;/p&gt;

&lt;h2 id=&quot;continuous-integration-and-drupal-projects&quot;&gt;Continuous integration and Drupal projects&lt;/h2&gt;

&lt;p&gt;This blog post is aimed at module maintainers, and we’ll look at how to use CI for modules hosted on Drupal.org. I’ll use as an example a project I’m maintaining, &lt;a href=&quot;https://www.drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The good news is that Drupal.org has a built-in CI service for hosted modules: to use it, project maintainers need to click on the “Automated Testing” tab of their projects, enable automated testing, and make sure some tests are defined.&lt;/p&gt;

&lt;p&gt;Once you have enabled automated testing, every submitted patch will be applied to the code and tested, and the main branches will be tested continually as well.&lt;/p&gt;

&lt;p&gt;If you’re not sure how to write tests, you can learn by example by looking at the test code of any module which has automated testing enabled.&lt;/p&gt;

&lt;h2 id=&quot;limitations-of-the-drupalorg-qa-system&quot;&gt;Limitations of the Drupal.org QA system&lt;/h2&gt;

&lt;p&gt;The system described above is great, and in this blog post we’ll explore how to take it a bit further. Drupal’s CI service runs your code on a new Drupal site with PHP 5.3 enabled. We know this by looking at &lt;a href=&quot;https://qa.drupal.org/pifr/test/787598&quot;&gt;the log for a test on Realistic Dummy content&lt;/a&gt;, which contains:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[13:50:02] Database backend [mysql] loaded.
...
[simpletest.db] =&amp;gt;
[test.php.version] =&amp;gt; 5.3
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the sake of this article, let’s say we want to use SQLite with php 5.5, and we also want to run checks from the &lt;a href=&quot;https://www.drupal.org/project/coder&quot;&gt;coder&lt;/a&gt; project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coder_review&lt;/code&gt; module. We can’t achieve this within the Drupal.org infrastructure, but it is possible using &lt;a href=&quot;https://www.docker.com&quot;&gt;Docker&lt;/a&gt;, &lt;a href=&quot;https://www.docker.com&quot;&gt;CircleCI&lt;/a&gt;, and &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt;. Here is how.&lt;/p&gt;

&lt;h2 id=&quot;step-1-get-a-local-coreosdocker-environment&quot;&gt;Step 1: get a local CoreOS+Docker environment&lt;/h2&gt;

&lt;p&gt;Let’s start by setting up a local development environment on which we can run Docker. Docker is a system which uses Linux containers to run your software and all its dependencies in an isolated environment.&lt;/p&gt;

&lt;p&gt;If you need a primer on Docker, check out &lt;a href=&quot;https://serversforhackers.com/getting-started-with-docker/&quot;&gt;Getting Started with Docker on Servers for Hackers (March 20, 2014)&lt;/a&gt;, and &lt;a href=&quot;http://blog.dcycle.com/blog/91/quick-intro-docker-drupal-project&quot;&gt;A quick intro to Docker for a Drupal project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Docker works best on CoreOS, which you can install quite easily on any computer using Vagrant and VirtualBox, as explained at &lt;a href=&quot;https://coreos.com/docs/running-coreos/platforms/vagrant/&quot;&gt;Running CoreOS on Vagrant&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;step-2-add-a-dockerfile-to-your-project&quot;&gt;Step 2: Add a Dockerfile to your project&lt;/h2&gt;

&lt;p&gt;Because, in this example, we want to run tests which require changing things on the server, we’ll use the Docker container management system to simulate a Ubuntu machine over which we have complete control.&lt;/p&gt;

&lt;p&gt;To see how this works, download the latest dev version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;realistic_dummy_content&lt;/code&gt; to your CoreOS VM, take a look at the included files &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./Dockerfile&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/test.sh&lt;/code&gt; to see how they are structured, then run the test script:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./scripts/test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Without any further configuration, you will see tests run on the desired environment: Ubuntu with the correct version of PHP, SQLite, and coder review. (You can also see the results on CircleCI &lt;a href=&quot;https://circleci.com/gh/alberto56/realistic_dummy_content/9&quot;&gt;on the project’s CI dashbaord&lt;/a&gt; if you unfold the “test” section – we’ll see how to set that up for your project later on).&lt;/p&gt;

&lt;p&gt;Setting up Docker for your own project is just a question of copy-pasting a few scripts.&lt;/p&gt;

&lt;h2 id=&quot;step-3-make-sure-there-is-a-mirror-of-your-project-on-github&quot;&gt;Step 3: Make sure there is a mirror of your project on GitHub&lt;/h2&gt;

&lt;p&gt;Having test results on your command line is nice, but there is no reason to run them yourself. For that we use continuous integration (CI) servers, which run the tests every time someone commits something to your codebase.&lt;/p&gt;

&lt;p&gt;Some of you might be familiar with &lt;a href=&quot;https://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt;, which I use myself and which is great, but for open source projects, there are free CI services out there: the two I know of, &lt;a href=&quot;https://circleci.com&quot;&gt;CircleCI&lt;/a&gt; and &lt;a href=&quot;https://travis-ci.org&quot;&gt;Travis CI&lt;/a&gt;, synchronize with GitHub, not with Drupal.org, so you need a mirror of your project on GitHub.&lt;/p&gt;

&lt;p&gt;Note that it is possible, using the tool &lt;a href=&quot;http://hubdrop.org&quot;&gt;HubDrop&lt;/a&gt;, to mirror your project on GitHub, but &lt;em&gt;it’s not on your account&lt;/em&gt;, whereas the CI tools sync only with projects on your own account. My solution has been to add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/mirror.sh&lt;/code&gt; script to Realistic Dummy Content, and call it once every ten minutes via a Jenkins job on my personal Jenkins server. If you don’t have access to a Jenkins server you can also use a cron job on any server to do this.&lt;/p&gt;

&lt;p&gt;The mirror of &lt;a href=&quot;http://drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt; on GitHub is &lt;a href=&quot;https://github.com/alberto56/realistic_dummy_content&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;step-4-open-a-circleci-account-and-link-it-to-your-github-account&quot;&gt;Step 4: Open a CircleCI account and link it to your GitHub account&lt;/h2&gt;

&lt;p&gt;As mentioned above, two of the CI tools out there are CircleCI and Travis CI. One of my requirements is that the CI tool integrate well with Docker, because that’s my DevOps tool of choice.&lt;/p&gt;

&lt;p&gt;As mentioned in &lt;a href=&quot;http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/&quot;&gt;Faster Builds with Container-Based Infrastructure and Docker (Mathias Meyer, Travis CI blog, 17 Dec. 2014)&lt;/a&gt;, it seems that Travis CI is moving towards Docker, but it seems that its new infrastructure is &lt;em&gt;based on Docker&lt;/em&gt;, but does not let you run your own Docker containers.&lt;/p&gt;

&lt;p&gt;Circle CI, on the other hand, seems to provide more flexibility with regards to Docker, as explained in the article &lt;a href=&quot;https://circleci.com/docs/docker&quot;&gt;Continuous Integration and Delivery with Docker&lt;/a&gt; on CircleCI’s website.&lt;/p&gt;

&lt;p&gt;Although Travis is a great, widely-used tool (&lt;a href=&quot;https://travis-ci.org/drush-ops/drush&quot;&gt;Drush uses it&lt;/a&gt;), we’ll use CircleCI because I found it easier to set up with Docker.&lt;/p&gt;

&lt;p&gt;Once you open a CircleCI account and link it to your GitHub account, you will be able to turn on CI for your mirrored project, in my case Realistic Dummy Content.&lt;/p&gt;

&lt;h2 id=&quot;step-5-add-a-circleyml-file-to-your-project&quot;&gt;Step 5: Add a circle.yml file to your project&lt;/h2&gt;

&lt;p&gt;In order for Circle CI to know what to do with your project, it needs a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;circle.yml&lt;/code&gt; file at the root of your project. If you look at the &lt;a href=&quot;http://cgit.drupalcode.org/realistic_dummy_content/tree/circle.yml&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;circle.yml&lt;/code&gt; file at the root Realistic Dummy Content&lt;/a&gt;, it is actually quite simple:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;machine:
  services:
    - docker

test:
  override:
    - ./scripts/test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it! Commit your circle.yml file, and if mirroring with GitHub works correctly, Circle CI will test your build. Debug any errors you may have, and &lt;em&gt;voilà!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://circleci.com/gh/alberto56/realistic_dummy_content/10&quot;&gt;Here is the result of a recent Realistic Dummy Content build on CircleCI&lt;/a&gt;: unfold the “test” section to see the complete output: PHP version, SQLite database, coder review…&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We have seen how you can easily add Docker support to make sure the tests and checks you run on your code are in a controlled environment, with the extensions you need (one could imagine a module which requires some external system like ApacheSolr installed on the server – Docker allows this too). This is one concrete application of DevOps: reducing the risk of glitches where “tests pass on my dev machine but not on my CI server”.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;Continuous integration (CI) is the practice of running a series of checks on every push of your code, to make sure it is always in a potentially deployable state; and to make sure you are alerted as soon as possible if it is not.&lt;/p&gt;

&lt;h2 id=&quot;continuous-integration-and-drupal-projects&quot;&gt;Continuous integration and Drupal projects&lt;/h2&gt;

&lt;p&gt;This blog post is aimed at module maintainers, and we’ll look at how to use CI for modules hosted on Drupal.org. I’ll use as an example a project I’m maintaining, &lt;a href=&quot;https://www.drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The good news is that Drupal.org has a built-in CI service for hosted modules: to use it, project maintainers need to click on the “Automated Testing” tab of their projects, enable automated testing, and make sure some tests are defined.&lt;/p&gt;

&lt;p&gt;Once you have enabled automated testing, every submitted patch will be applied to the code and tested, and the main branches will be tested continually as well.&lt;/p&gt;

&lt;p&gt;If you’re not sure how to write tests, you can learn by example by looking at the test code of any module which has automated testing enabled.&lt;/p&gt;

&lt;h2 id=&quot;limitations-of-the-drupalorg-qa-system&quot;&gt;Limitations of the Drupal.org QA system&lt;/h2&gt;

&lt;p&gt;The system described above is great, and in this blog post we’ll explore how to take it a bit further. Drupal’s CI service runs your code on a new Drupal site with PHP 5.3 enabled. We know this by looking at &lt;a href=&quot;https://qa.drupal.org/pifr/test/787598&quot;&gt;the log for a test on Realistic Dummy content&lt;/a&gt;, which contains:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[13:50:02] Database backend [mysql] loaded.
...
[simpletest.db] =&amp;gt;
[test.php.version] =&amp;gt; 5.3
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the sake of this article, let’s say we want to use SQLite with php 5.5, and we also want to run checks from the &lt;a href=&quot;https://www.drupal.org/project/coder&quot;&gt;coder&lt;/a&gt; project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;coder_review&lt;/code&gt; module. We can’t achieve this within the Drupal.org infrastructure, but it is possible using &lt;a href=&quot;https://www.docker.com&quot;&gt;Docker&lt;/a&gt;, &lt;a href=&quot;https://www.docker.com&quot;&gt;CircleCI&lt;/a&gt;, and &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt;. Here is how.&lt;/p&gt;

&lt;h2 id=&quot;step-1-get-a-local-coreosdocker-environment&quot;&gt;Step 1: get a local CoreOS+Docker environment&lt;/h2&gt;

&lt;p&gt;Let’s start by setting up a local development environment on which we can run Docker. Docker is a system which uses Linux containers to run your software and all its dependencies in an isolated environment.&lt;/p&gt;

&lt;p&gt;If you need a primer on Docker, check out &lt;a href=&quot;https://serversforhackers.com/getting-started-with-docker/&quot;&gt;Getting Started with Docker on Servers for Hackers (March 20, 2014)&lt;/a&gt;, and &lt;a href=&quot;http://blog.dcycle.com/blog/91/quick-intro-docker-drupal-project&quot;&gt;A quick intro to Docker for a Drupal project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Docker works best on CoreOS, which you can install quite easily on any computer using Vagrant and VirtualBox, as explained at &lt;a href=&quot;https://coreos.com/docs/running-coreos/platforms/vagrant/&quot;&gt;Running CoreOS on Vagrant&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;step-2-add-a-dockerfile-to-your-project&quot;&gt;Step 2: Add a Dockerfile to your project&lt;/h2&gt;

&lt;p&gt;Because, in this example, we want to run tests which require changing things on the server, we’ll use the Docker container management system to simulate a Ubuntu machine over which we have complete control.&lt;/p&gt;

&lt;p&gt;To see how this works, download the latest dev version of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;realistic_dummy_content&lt;/code&gt; to your CoreOS VM, take a look at the included files &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./Dockerfile&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/test.sh&lt;/code&gt; to see how they are structured, then run the test script:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./scripts/test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Without any further configuration, you will see tests run on the desired environment: Ubuntu with the correct version of PHP, SQLite, and coder review. (You can also see the results on CircleCI &lt;a href=&quot;https://circleci.com/gh/alberto56/realistic_dummy_content/9&quot;&gt;on the project’s CI dashbaord&lt;/a&gt; if you unfold the “test” section – we’ll see how to set that up for your project later on).&lt;/p&gt;

&lt;p&gt;Setting up Docker for your own project is just a question of copy-pasting a few scripts.&lt;/p&gt;

&lt;h2 id=&quot;step-3-make-sure-there-is-a-mirror-of-your-project-on-github&quot;&gt;Step 3: Make sure there is a mirror of your project on GitHub&lt;/h2&gt;

&lt;p&gt;Having test results on your command line is nice, but there is no reason to run them yourself. For that we use continuous integration (CI) servers, which run the tests every time someone commits something to your codebase.&lt;/p&gt;

&lt;p&gt;Some of you might be familiar with &lt;a href=&quot;https://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt;, which I use myself and which is great, but for open source projects, there are free CI services out there: the two I know of, &lt;a href=&quot;https://circleci.com&quot;&gt;CircleCI&lt;/a&gt; and &lt;a href=&quot;https://travis-ci.org&quot;&gt;Travis CI&lt;/a&gt;, synchronize with GitHub, not with Drupal.org, so you need a mirror of your project on GitHub.&lt;/p&gt;

&lt;p&gt;Note that it is possible, using the tool &lt;a href=&quot;http://hubdrop.org&quot;&gt;HubDrop&lt;/a&gt;, to mirror your project on GitHub, but &lt;em&gt;it’s not on your account&lt;/em&gt;, whereas the CI tools sync only with projects on your own account. My solution has been to add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/mirror.sh&lt;/code&gt; script to Realistic Dummy Content, and call it once every ten minutes via a Jenkins job on my personal Jenkins server. If you don’t have access to a Jenkins server you can also use a cron job on any server to do this.&lt;/p&gt;

&lt;p&gt;The mirror of &lt;a href=&quot;http://drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt; on GitHub is &lt;a href=&quot;https://github.com/alberto56/realistic_dummy_content&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;step-4-open-a-circleci-account-and-link-it-to-your-github-account&quot;&gt;Step 4: Open a CircleCI account and link it to your GitHub account&lt;/h2&gt;

&lt;p&gt;As mentioned above, two of the CI tools out there are CircleCI and Travis CI. One of my requirements is that the CI tool integrate well with Docker, because that’s my DevOps tool of choice.&lt;/p&gt;

&lt;p&gt;As mentioned in &lt;a href=&quot;http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/&quot;&gt;Faster Builds with Container-Based Infrastructure and Docker (Mathias Meyer, Travis CI blog, 17 Dec. 2014)&lt;/a&gt;, it seems that Travis CI is moving towards Docker, but it seems that its new infrastructure is &lt;em&gt;based on Docker&lt;/em&gt;, but does not let you run your own Docker containers.&lt;/p&gt;

&lt;p&gt;Circle CI, on the other hand, seems to provide more flexibility with regards to Docker, as explained in the article &lt;a href=&quot;https://circleci.com/docs/docker&quot;&gt;Continuous Integration and Delivery with Docker&lt;/a&gt; on CircleCI’s website.&lt;/p&gt;

&lt;p&gt;Although Travis is a great, widely-used tool (&lt;a href=&quot;https://travis-ci.org/drush-ops/drush&quot;&gt;Drush uses it&lt;/a&gt;), we’ll use CircleCI because I found it easier to set up with Docker.&lt;/p&gt;

&lt;p&gt;Once you open a CircleCI account and link it to your GitHub account, you will be able to turn on CI for your mirrored project, in my case Realistic Dummy Content.&lt;/p&gt;

&lt;h2 id=&quot;step-5-add-a-circleyml-file-to-your-project&quot;&gt;Step 5: Add a circle.yml file to your project&lt;/h2&gt;

&lt;p&gt;In order for Circle CI to know what to do with your project, it needs a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;circle.yml&lt;/code&gt; file at the root of your project. If you look at the &lt;a href=&quot;http://cgit.drupalcode.org/realistic_dummy_content/tree/circle.yml&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;circle.yml&lt;/code&gt; file at the root Realistic Dummy Content&lt;/a&gt;, it is actually quite simple:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;machine:
  services:
    - docker

test:
  override:
    - ./scripts/test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it! Commit your circle.yml file, and if mirroring with GitHub works correctly, Circle CI will test your build. Debug any errors you may have, and &lt;em&gt;voilà!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://circleci.com/gh/alberto56/realistic_dummy_content/10&quot;&gt;Here is the result of a recent Realistic Dummy Content build on CircleCI&lt;/a&gt;: unfold the “test” section to see the complete output: PHP version, SQLite database, coder review…&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We have seen how you can easily add Docker support to make sure the tests and checks you run on your code are in a controlled environment, with the extensions you need (one could imagine a module which requires some external system like ApacheSolr installed on the server – Docker allows this too). This is one concrete application of DevOps: reducing the risk of glitches where “tests pass on my dev machine but not on my CI server”.&lt;/p&gt;
</description>
        
        <pubDate>Mon, 23 Feb 2015 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/92/continuous-integration-circle-ci-and-docker-your-drupal-project/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/92/continuous-integration-circle-ci-and-docker-your-drupal-project/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>A quick intro to Docker for a Drupal project</title>
        <description>&lt;p&gt;I recently &lt;a href=&quot;https://www.drupal.org/node/2428203&quot;&gt;added Docker support to Realistic Dummy Content&lt;/a&gt;, a project I maintain on Drupal.org. It is now possible (with Docker installed, preferably on a &lt;a href=&quot;https://coreos.com/docs/running-coreos/platforms/vagrant/&quot;&gt;CoreOS VM&lt;/a&gt;) to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/dev.sh&lt;/code&gt; directly from the project directory (use the latest dev version if you try this), and have a development environment, &lt;em&gt;sans&lt;/em&gt; MAMP.&lt;/p&gt;

&lt;p&gt;I don’t consider myself an expert in Docker, virtualization, DevOps and config management, but here, nonetheless, is my experience. If I’m wrong about something, please leave a comment!&lt;/p&gt;

&lt;h2 id=&quot;intro-docker-and-devops&quot;&gt;Intro: Docker and DevOps&lt;/h2&gt;

&lt;p&gt;The DevOps movement, popularized starting in about 2010, promises to include environment information along with application information in the same git repo for smoother development, testing, and production environments. For example, if your Drupal module requires version 5.4 of PHP, along with a given library, then that information should be somewhere in your Git repo. Building an environment for testing, development or production should then use that information and not be dependent on anything which is unversioned. Docker is a tool which is anchored in the DevOps movement.&lt;/p&gt;

&lt;h2 id=&quot;devops-the-config-management-approach&quot;&gt;DevOps: the Config management approach&lt;/h2&gt;

&lt;p&gt;The family of tools which has been around for awhile now includes &lt;a href=&quot;http://puppetlabs.com&quot;&gt;Puppet&lt;/a&gt;, &lt;a href=&quot;https://www.chef.io&quot;&gt;Chef&lt;/a&gt;, and &lt;a href=&quot;http://www.ansible.com/home&quot;&gt;Ansible&lt;/a&gt;. These tools are configuration management tools: they define environment information (PHP version should be 5.3, Apache mod_rewrite should be on, etc.) and make sure a given environment conforms to that information.&lt;/p&gt;

&lt;p&gt;I have used Puppet, along with &lt;a href=&quot;https://www.vagrantup.com&quot;&gt;Vagrant&lt;/a&gt;, to deliver applications, including my &lt;a href=&quot;https://github.com/alberto56/vagrant-jenkins&quot;&gt;Jenkins server hosted on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;virtualization-and-containers&quot;&gt;Virtualization and containers&lt;/h2&gt;

&lt;p&gt;Using Puppet and Vagrant, you need to use Virtualization: create a Virtual Machine on your host machine.&lt;/p&gt;

&lt;p&gt;Docker works with a different principle: instead of creating a VM on top of your host OS, Docker uses containers, so resources are shared. The article &lt;a href=&quot;https://serversforhackers.com/getting-started-with-docker/&quot;&gt;Getting Started with Docker (Servers for Hackers, 2014/03/20)&lt;/a&gt; contains some graphics which demonstrate how much more efficient containers are as opposed to virtualization.&lt;/p&gt;

&lt;h2 id=&quot;puppet-and-vagrant-are-slow-docker-is-fast&quot;&gt;Puppet and Vagrant are slow; Docker is fast&lt;/h2&gt;

&lt;p&gt;Puppet and Vagrant together work for packaging software and environment configuration, but it is excruciatingly slow: it can take several minutes to launch an environment. My reaction to this has been to cringe every time I have to do it.&lt;/p&gt;

&lt;p&gt;Docker, on the other hand, uses caching agressively: if a server was already in a given state, Docker uses a cached version of it to move along faster. So, when building a container, Docker goes through a series of steps, and caches each step to make it lightning fast.&lt;/p&gt;

&lt;p&gt;One example: launching a dev environment of &lt;a href=&quot;https://github.com/alberto56/vagrant-jenkins&quot;&gt;the Jenkins Vagrant project&lt;/a&gt; on Mac OS takes over five minutes, but launching a dev environment of my Drupal project &lt;a href=&quot;https://www.drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt; (which uses Docker), takes less than 15 seconds the first time it is run once the server code has been downloaded, &lt;em&gt;and, because of caching, less than one (1) second&lt;/em&gt; subsequent times if no changes have been made. &lt;em&gt;Less than one second to fire up a full-fledged development environment which is functionally independent from your host&lt;/em&gt;. That’s huge to me.&lt;/p&gt;

&lt;h2 id=&quot;configuration-management-is-idempotent-docker-is-not&quot;&gt;Configuration management is idempotent, Docker is not&lt;/h2&gt;

&lt;p&gt;Before we move on, note that Docker is not incompatible with config management tools, but Docker does not require them. Here is why I think, in many cases, config management tools are not necessary.&lt;/p&gt;

&lt;p&gt;The config management tools such as Puppet are &lt;em&gt;idempotent&lt;/em&gt;: you define how an environment should be, and the tools run whatever steps are necessary to make it that way. This sounds like a good idea in theory, but it &lt;a href=&quot;https://github.com/alberto56/vagrant-jenkins/blob/master/manifests/init.pp&quot;&gt;looks like this&lt;/a&gt; in practice. I have come to the conclusion that this is not the way I think, and it forces me to relearn how to think of my environments. I suspect that many developers have a hard time wrapping their heads around idempotence.&lt;/p&gt;

&lt;p&gt;Docker is not idempotent; it defines a series of steps to get to a given state. If you like idempotence, one of the steps can be to run a puppet manifest; but if, like me, you think idempotence is overrated, then you don’t need to use it. &lt;a href=&quot;https://github.com/b7alt/drupal/blob/master/Dockerfile&quot;&gt;Here is what a Dockerfile looks like&lt;/a&gt;: I understood it at first glace, it doesn’t require me to learn a new way of thinking.&lt;/p&gt;

&lt;h2 id=&quot;the-coreos-project&quot;&gt;The CoreOS project&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://coreos.com&quot;&gt;CoreOS&lt;/a&gt; project has seen the promise of Docker and containers. It is an OS which ships with Docker, Git, and a few other tools, but is designed so that everything you do happens within containers (using the included Docker, and eventually &lt;a href=&quot;https://coreos.com/blog/rocket/&quot;&gt;Rocket&lt;/a&gt;, a tool they are building). The result is that CoreOS is tiny: it takes 10 seconds to build a CoreOS instance on &lt;a href=&quot;https://www.digitalocean.com&quot;&gt;DigitalOcean&lt;/a&gt;, for example, but almost a minute to set up a CentOS instance.&lt;/p&gt;

&lt;p&gt;Because Docker does not work on Mac OS &lt;a href=&quot;https://docs.docker.com/installation/mac/&quot;&gt;without going through hoops&lt;/a&gt;, I decided to use &lt;a href=&quot;https://coreos.com/docs/running-coreos/platforms/vagrant/&quot;&gt;Vagrant to set up a CoreOS VM on my Mac&lt;/a&gt;, which is speedy and works great.&lt;/p&gt;

&lt;h2 id=&quot;docker-for-deploying-to-production&quot;&gt;Docker for deploying to production&lt;/h2&gt;

&lt;p&gt;We have seen that Docker can work for quickly setting up dev and testing environments. Can it be used to deploy to production? I don’t see why not, especially if used with CoreOS. For an example see the blog post &lt;a href=&quot;http://www.shopify.ca/technology/15563928-building-an-internal-cloud-with-docker-and-coreos&quot;&gt;Building an Internal Cloud with Docker and CoreOS (Shopify, Oct. 15, 2014)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In conclusion, I am just beginning to play with Docker, and it just feels right to me. I remember working with &lt;a href=&quot;http://www.joomla.org&quot;&gt;Joomla&lt;/a&gt; in 2006, when I discovered &lt;a href=&quot;https://www.drupal.org&quot;&gt;Drupal&lt;/a&gt;, and &lt;em&gt;it just felt right&lt;/em&gt;, and I have made a career of it since then. I am having the same feeling now discovering Docker and CoreOs.&lt;/p&gt;

&lt;p&gt;I am looking forward to your comments explaining why I am wrong about not liking idempotence, how to make config management and virutalization faster, and how and why to integrate config management tools with Docker!&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;I recently &lt;a href=&quot;https://www.drupal.org/node/2428203&quot;&gt;added Docker support to Realistic Dummy Content&lt;/a&gt;, a project I maintain on Drupal.org. It is now possible (with Docker installed, preferably on a &lt;a href=&quot;https://coreos.com/docs/running-coreos/platforms/vagrant/&quot;&gt;CoreOS VM&lt;/a&gt;) to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./scripts/dev.sh&lt;/code&gt; directly from the project directory (use the latest dev version if you try this), and have a development environment, &lt;em&gt;sans&lt;/em&gt; MAMP.&lt;/p&gt;

&lt;p&gt;I don’t consider myself an expert in Docker, virtualization, DevOps and config management, but here, nonetheless, is my experience. If I’m wrong about something, please leave a comment!&lt;/p&gt;

&lt;h2 id=&quot;intro-docker-and-devops&quot;&gt;Intro: Docker and DevOps&lt;/h2&gt;

&lt;p&gt;The DevOps movement, popularized starting in about 2010, promises to include environment information along with application information in the same git repo for smoother development, testing, and production environments. For example, if your Drupal module requires version 5.4 of PHP, along with a given library, then that information should be somewhere in your Git repo. Building an environment for testing, development or production should then use that information and not be dependent on anything which is unversioned. Docker is a tool which is anchored in the DevOps movement.&lt;/p&gt;

&lt;h2 id=&quot;devops-the-config-management-approach&quot;&gt;DevOps: the Config management approach&lt;/h2&gt;

&lt;p&gt;The family of tools which has been around for awhile now includes &lt;a href=&quot;http://puppetlabs.com&quot;&gt;Puppet&lt;/a&gt;, &lt;a href=&quot;https://www.chef.io&quot;&gt;Chef&lt;/a&gt;, and &lt;a href=&quot;http://www.ansible.com/home&quot;&gt;Ansible&lt;/a&gt;. These tools are configuration management tools: they define environment information (PHP version should be 5.3, Apache mod_rewrite should be on, etc.) and make sure a given environment conforms to that information.&lt;/p&gt;

&lt;p&gt;I have used Puppet, along with &lt;a href=&quot;https://www.vagrantup.com&quot;&gt;Vagrant&lt;/a&gt;, to deliver applications, including my &lt;a href=&quot;https://github.com/alberto56/vagrant-jenkins&quot;&gt;Jenkins server hosted on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;virtualization-and-containers&quot;&gt;Virtualization and containers&lt;/h2&gt;

&lt;p&gt;Using Puppet and Vagrant, you need to use Virtualization: create a Virtual Machine on your host machine.&lt;/p&gt;

&lt;p&gt;Docker works with a different principle: instead of creating a VM on top of your host OS, Docker uses containers, so resources are shared. The article &lt;a href=&quot;https://serversforhackers.com/getting-started-with-docker/&quot;&gt;Getting Started with Docker (Servers for Hackers, 2014/03/20)&lt;/a&gt; contains some graphics which demonstrate how much more efficient containers are as opposed to virtualization.&lt;/p&gt;

&lt;h2 id=&quot;puppet-and-vagrant-are-slow-docker-is-fast&quot;&gt;Puppet and Vagrant are slow; Docker is fast&lt;/h2&gt;

&lt;p&gt;Puppet and Vagrant together work for packaging software and environment configuration, but it is excruciatingly slow: it can take several minutes to launch an environment. My reaction to this has been to cringe every time I have to do it.&lt;/p&gt;

&lt;p&gt;Docker, on the other hand, uses caching agressively: if a server was already in a given state, Docker uses a cached version of it to move along faster. So, when building a container, Docker goes through a series of steps, and caches each step to make it lightning fast.&lt;/p&gt;

&lt;p&gt;One example: launching a dev environment of &lt;a href=&quot;https://github.com/alberto56/vagrant-jenkins&quot;&gt;the Jenkins Vagrant project&lt;/a&gt; on Mac OS takes over five minutes, but launching a dev environment of my Drupal project &lt;a href=&quot;https://www.drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt; (which uses Docker), takes less than 15 seconds the first time it is run once the server code has been downloaded, &lt;em&gt;and, because of caching, less than one (1) second&lt;/em&gt; subsequent times if no changes have been made. &lt;em&gt;Less than one second to fire up a full-fledged development environment which is functionally independent from your host&lt;/em&gt;. That’s huge to me.&lt;/p&gt;

&lt;h2 id=&quot;configuration-management-is-idempotent-docker-is-not&quot;&gt;Configuration management is idempotent, Docker is not&lt;/h2&gt;

&lt;p&gt;Before we move on, note that Docker is not incompatible with config management tools, but Docker does not require them. Here is why I think, in many cases, config management tools are not necessary.&lt;/p&gt;

&lt;p&gt;The config management tools such as Puppet are &lt;em&gt;idempotent&lt;/em&gt;: you define how an environment should be, and the tools run whatever steps are necessary to make it that way. This sounds like a good idea in theory, but it &lt;a href=&quot;https://github.com/alberto56/vagrant-jenkins/blob/master/manifests/init.pp&quot;&gt;looks like this&lt;/a&gt; in practice. I have come to the conclusion that this is not the way I think, and it forces me to relearn how to think of my environments. I suspect that many developers have a hard time wrapping their heads around idempotence.&lt;/p&gt;

&lt;p&gt;Docker is not idempotent; it defines a series of steps to get to a given state. If you like idempotence, one of the steps can be to run a puppet manifest; but if, like me, you think idempotence is overrated, then you don’t need to use it. &lt;a href=&quot;https://github.com/b7alt/drupal/blob/master/Dockerfile&quot;&gt;Here is what a Dockerfile looks like&lt;/a&gt;: I understood it at first glace, it doesn’t require me to learn a new way of thinking.&lt;/p&gt;

&lt;h2 id=&quot;the-coreos-project&quot;&gt;The CoreOS project&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://coreos.com&quot;&gt;CoreOS&lt;/a&gt; project has seen the promise of Docker and containers. It is an OS which ships with Docker, Git, and a few other tools, but is designed so that everything you do happens within containers (using the included Docker, and eventually &lt;a href=&quot;https://coreos.com/blog/rocket/&quot;&gt;Rocket&lt;/a&gt;, a tool they are building). The result is that CoreOS is tiny: it takes 10 seconds to build a CoreOS instance on &lt;a href=&quot;https://www.digitalocean.com&quot;&gt;DigitalOcean&lt;/a&gt;, for example, but almost a minute to set up a CentOS instance.&lt;/p&gt;

&lt;p&gt;Because Docker does not work on Mac OS &lt;a href=&quot;https://docs.docker.com/installation/mac/&quot;&gt;without going through hoops&lt;/a&gt;, I decided to use &lt;a href=&quot;https://coreos.com/docs/running-coreos/platforms/vagrant/&quot;&gt;Vagrant to set up a CoreOS VM on my Mac&lt;/a&gt;, which is speedy and works great.&lt;/p&gt;

&lt;h2 id=&quot;docker-for-deploying-to-production&quot;&gt;Docker for deploying to production&lt;/h2&gt;

&lt;p&gt;We have seen that Docker can work for quickly setting up dev and testing environments. Can it be used to deploy to production? I don’t see why not, especially if used with CoreOS. For an example see the blog post &lt;a href=&quot;http://www.shopify.ca/technology/15563928-building-an-internal-cloud-with-docker-and-coreos&quot;&gt;Building an Internal Cloud with Docker and CoreOS (Shopify, Oct. 15, 2014)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In conclusion, I am just beginning to play with Docker, and it just feels right to me. I remember working with &lt;a href=&quot;http://www.joomla.org&quot;&gt;Joomla&lt;/a&gt; in 2006, when I discovered &lt;a href=&quot;https://www.drupal.org&quot;&gt;Drupal&lt;/a&gt;, and &lt;em&gt;it just felt right&lt;/em&gt;, and I have made a career of it since then. I am having the same feeling now discovering Docker and CoreOs.&lt;/p&gt;

&lt;p&gt;I am looking forward to your comments explaining why I am wrong about not liking idempotence, how to make config management and virutalization faster, and how and why to integrate config management tools with Docker!&lt;/p&gt;
</description>
        
        <pubDate>Wed, 18 Feb 2015 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/91/quick-intro-docker-drupal-project/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/91/quick-intro-docker-drupal-project/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Drupal and Docker: Creating a new Docker image based on an existing image</title>
        <description>&lt;p&gt;To get the most of this blog post, please read and understand &lt;a href=&quot;https://serversforhackers.com/getting-started-with-docker/&quot;&gt;Getting Started with Docker (Servers for Hackers, 2014/03/20)&lt;/a&gt;. Also, all the steps outlined here have been done on a &lt;a href=&quot;https://coreos.com/docs/running-coreos/platforms/vagrant/&quot;&gt;Vagrant CoreOS virtual machine (VM)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I recently needed a really simple non-production Drupal Docker image on which I could run tests. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b7alt/drupal&lt;/code&gt; (which you can find by typing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker search drupal&lt;/code&gt;, or &lt;a href=&quot;https://github.com/b7alt/drupal&quot;&gt;on GitHub&lt;/a&gt;) worked for my needs, except that it did not have the cUrl php library installed, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush en simpletest -y&lt;/code&gt; was throwing an error.&lt;/p&gt;

&lt;p&gt;Therefore, I decided to create a new Docker image which is based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b7alt/drupal&lt;/code&gt;, but with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php5-curl&lt;/code&gt; library installed.&lt;/p&gt;

&lt;p&gt;I started by creating a new local directory (on my CoreOS VM), which I called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-drupal&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir docker-drupal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In that directory, I created &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; which takes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b7alt/drupal&lt;/code&gt; as its base, and runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get install curl&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM b7alt/drupal

RUN apt-get update
RUN apt-get -y install curl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(You can find this code at my GitHub account at &lt;a href=&quot;https://github.com/alberto56/docker-drupal&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alberto56/docker-drupal&lt;/code&gt;&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;When you run this you will get:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build .
...
Successfully built 55a8c8999520
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That hash is a Docker image ID, and your hash might be different. You can run it and see if it works as expected:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run -d 55a8c8999520
c9a98bdcab4e027e8571bde71ee92b4380247a44ef9314749ef5680864de2928
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above, we are telling Docker to create a container based on the image we just created (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;55a8c8999520&lt;/code&gt;). The resulting container hash is displayed (yours might be different). We are using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-d&lt;/code&gt; so that our containers runs in the background. You can see that the container is actually running by typing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker ps
CONTAINER ID        IMAGE               COMMAND...
c9a98bdcab4e        55a8c8999520        &quot;/usr/bin/supervisor...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This tells you that there is a running container (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c9a98bdcab4e&lt;/code&gt;) based on the image &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;55a8c8999520&lt;/code&gt;. Again, your hases will be different. Let’s log into that container now:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker exec -it c9a98bdcab4e bash
root@c9a98bdcab4e:/#
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make sure that cUrl is successfully installed, I will figure out where Drupal resides on this container, and then try to enable Simpletest. If that works, I will consider my image a success, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exit&lt;/code&gt; from my container:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@c9a98bdcab4e:/# find / -name &apos;index.php&apos;
/srv/drupal/www/index.php
root@c9a98bdcab4e:/# cd /srv/drupal/www
root@c9a98bdcab4e:/srv/drupal/www# drush en simpletest -y
The following extensions will be enabled: simpletest
Do you really want to continue? (y/n): y
simpletest was enabled successfully.                   [ok]
root@c9a98bdcab4e:/srv/drupal/www# exit
exit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now I know that my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;55a8c8999520&lt;/code&gt; image is good for now and for my purposes; I can create an account on &lt;a href=&quot;https://www.docker.com&quot;&gt;Docker.com&lt;/a&gt; and push it to my account for later use:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Docker build -t alberto56/docker-drupal .
docker push alberto56/docker-drupal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Anyone can now run this Docker image by simply typing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run alberto56/docker-drupal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One thing I had a hard time getting my head around was having a GitHub project and Docker project, and both are different but linked. The GitHub project is the the recipe for creating an image, whereas the Docker project is the image itself.&lt;/p&gt;

&lt;p&gt;One we start thinking of our environments like this (as entities which should be versioned and shared), the risk of differences between environments is greatly reduced. I was used to running simpletests for my projects on an environment which is managed by hand; when I got a strange permissions error on the test environment, I decided to start using Docker and version control to manage the container where tests are run.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;To get the most of this blog post, please read and understand &lt;a href=&quot;https://serversforhackers.com/getting-started-with-docker/&quot;&gt;Getting Started with Docker (Servers for Hackers, 2014/03/20)&lt;/a&gt;. Also, all the steps outlined here have been done on a &lt;a href=&quot;https://coreos.com/docs/running-coreos/platforms/vagrant/&quot;&gt;Vagrant CoreOS virtual machine (VM)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I recently needed a really simple non-production Drupal Docker image on which I could run tests. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b7alt/drupal&lt;/code&gt; (which you can find by typing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker search drupal&lt;/code&gt;, or &lt;a href=&quot;https://github.com/b7alt/drupal&quot;&gt;on GitHub&lt;/a&gt;) worked for my needs, except that it did not have the cUrl php library installed, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush en simpletest -y&lt;/code&gt; was throwing an error.&lt;/p&gt;

&lt;p&gt;Therefore, I decided to create a new Docker image which is based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b7alt/drupal&lt;/code&gt;, but with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;php5-curl&lt;/code&gt; library installed.&lt;/p&gt;

&lt;p&gt;I started by creating a new local directory (on my CoreOS VM), which I called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-drupal&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir docker-drupal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In that directory, I created &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt; which takes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b7alt/drupal&lt;/code&gt; as its base, and runs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt-get install curl&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FROM b7alt/drupal

RUN apt-get update
RUN apt-get -y install curl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(You can find this code at my GitHub account at &lt;a href=&quot;https://github.com/alberto56/docker-drupal&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alberto56/docker-drupal&lt;/code&gt;&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;When you run this you will get:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build .
...
Successfully built 55a8c8999520
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That hash is a Docker image ID, and your hash might be different. You can run it and see if it works as expected:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run -d 55a8c8999520
c9a98bdcab4e027e8571bde71ee92b4380247a44ef9314749ef5680864de2928
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above, we are telling Docker to create a container based on the image we just created (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;55a8c8999520&lt;/code&gt;). The resulting container hash is displayed (yours might be different). We are using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-d&lt;/code&gt; so that our containers runs in the background. You can see that the container is actually running by typing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker ps
CONTAINER ID        IMAGE               COMMAND...
c9a98bdcab4e        55a8c8999520        &quot;/usr/bin/supervisor...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This tells you that there is a running container (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c9a98bdcab4e&lt;/code&gt;) based on the image &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;55a8c8999520&lt;/code&gt;. Again, your hases will be different. Let’s log into that container now:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker exec -it c9a98bdcab4e bash
root@c9a98bdcab4e:/#
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make sure that cUrl is successfully installed, I will figure out where Drupal resides on this container, and then try to enable Simpletest. If that works, I will consider my image a success, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exit&lt;/code&gt; from my container:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@c9a98bdcab4e:/# find / -name &apos;index.php&apos;
/srv/drupal/www/index.php
root@c9a98bdcab4e:/# cd /srv/drupal/www
root@c9a98bdcab4e:/srv/drupal/www# drush en simpletest -y
The following extensions will be enabled: simpletest
Do you really want to continue? (y/n): y
simpletest was enabled successfully.                   [ok]
root@c9a98bdcab4e:/srv/drupal/www# exit
exit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now I know that my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;55a8c8999520&lt;/code&gt; image is good for now and for my purposes; I can create an account on &lt;a href=&quot;https://www.docker.com&quot;&gt;Docker.com&lt;/a&gt; and push it to my account for later use:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Docker build -t alberto56/docker-drupal .
docker push alberto56/docker-drupal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Anyone can now run this Docker image by simply typing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run alberto56/docker-drupal
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One thing I had a hard time getting my head around was having a GitHub project and Docker project, and both are different but linked. The GitHub project is the the recipe for creating an image, whereas the Docker project is the image itself.&lt;/p&gt;

&lt;p&gt;One we start thinking of our environments like this (as entities which should be versioned and shared), the risk of differences between environments is greatly reduced. I was used to running simpletests for my projects on an environment which is managed by hand; when I got a strange permissions error on the test environment, I decided to start using Docker and version control to manage the container where tests are run.&lt;/p&gt;
</description>
        
        <pubDate>Mon, 09 Feb 2015 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/89/drupal-and-docker-creating-new-docker-image-based-existing-image/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/89/drupal-and-docker-creating-new-docker-image-based-existing-image/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Two tips for debugging Simpletest tests</title>
        <description>&lt;p&gt;I have been using Simpletest on Drupal 7 for several years, and, used well, it can greatly enhance the quality of your code. I like to practice &lt;a href=&quot;http://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;test-driven development&lt;/a&gt;: writing a failing test first, then run it multiple times, each time tweaking the code, until the test passes.&lt;/p&gt;

&lt;p&gt;Simpletest works by spawning a completely new Drupal site (ignoring your current database), running tests, and destroying the database. Sometimes, a test will fail and you’re not quite sure why. Here are two tips to help you debug why your tests are failing:&lt;/p&gt;

&lt;h2 id=&quot;tip-1-debug&quot;&gt;Tip #1: debug()&lt;/h2&gt;

&lt;p&gt;The Drupal &lt;a href=&quot;https://api.drupal.org/api/drupal/includes!common.inc/function/debug/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debug()&lt;/code&gt; function&lt;/a&gt; can be placed &lt;em&gt;anywhere in your test or your source code&lt;/em&gt;, and the result will appear on the test  results page in the GUI.&lt;/p&gt;

&lt;p&gt;For example, if when you are playing around with the dev version of your site, things work fine, but in the test, a specific node contains invalid data, you can add this line anywhere in your test or source code which is being called during your test:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
debug($node);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will provide formatted output of your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$node&lt;/code&gt; variable, alongside your test results.&lt;/p&gt;

&lt;h2 id=&quot;tip-2-die&quot;&gt;Tip #2: die()&lt;/h2&gt;

&lt;p&gt;Sometimes the temporary test environment’s behaviour seems to make no sense. And it can be frustrating to not be able to simply log into it and play around with it, because it is destroyed after the test is over.&lt;/p&gt;

&lt;p&gt;To understand this technique, here is quick primer on how Simpletest works:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In Drupal 7, running a test requires a host site and database. This is basically an installed Drupal site with Simpletest enabled, and your module somewhere in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modules&lt;/code&gt; directory (the module you are testing does not have to be enabled).&lt;/li&gt;
  &lt;li&gt;When you run a test, Simpletest creates a brand-new installation of Drupal using a special prefix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simpletest123456&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;123456&lt;/code&gt; is a random number. This allows Simpletest to have an isolated environment where to run tests, but on the same database and with the same credentials as the host.&lt;/li&gt;
  &lt;li&gt;When your test does something, like call a function, or load a page with, for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$this-&amp;gt;drupalGet(&apos;user&apos;)&lt;/code&gt;, the host environment is ignored and temporary environment (which uses the prefixed database tables) is used. In the previous example, the test loads the “user” page using a real HTTP calls. Simpletest knows to use the temporary environment because the call is made using a specially-crafted user agent.&lt;/li&gt;
  &lt;li&gt;When the test is over, all tables with the prefix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simpletest123456&lt;/code&gt; are destroyed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have ever tried to run a test on a host environment which already contains a prefix, you will understand why you can get “table name too long” errors in certain cases: Simpletest is trying to add a prefix to another prefix. That’s one reason to avoid prefixes when you can, but I digress.&lt;/p&gt;

&lt;p&gt;Now you can try this: somewhere in your test code, add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;die()&lt;/code&gt;, this will kill Simpletest, leaving the temporary database intact.&lt;/p&gt;

&lt;p&gt;Here is an example: a colleague recently was testing a feature which exported a view. In the dev environment, the view was available to users with the role &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manager&lt;/code&gt;, as was expected. However when the test logged in as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manager&lt;/code&gt; user and attempted to access the view, the result was an “Access denied” page.&lt;/p&gt;

&lt;p&gt;Because we couldn’t easily figure it out, I suggested adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;die()&lt;/code&gt; to play around in the environment:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$this-&amp;gt;drupalLogin($manager);
$this-&amp;gt;drupalGet(&apos;inventory&apos;);
die();
$this-&amp;gt;assertNoText(&apos;denied&apos;, &apos;A manager accessing the inventory page does not see &quot;access denied&quot;&apos;);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, when the test was run, we could:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;wait for it to crash,&lt;/li&gt;
  &lt;li&gt;then examine our database to figure out which prefix the test was using,&lt;/li&gt;
  &lt;li&gt;change the database prefix in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/default/settings.php&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;&apos;&lt;/code&gt; to (for example) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;simpletest73845&apos;&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush uli&lt;/code&gt; to get a one-time login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, it was easier to debug the source of the problem by visiting the views configuration for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inventory&lt;/code&gt;: it turns out that features exports views with access by role using the role ID, not the role name (the role ID can be different for each environment). Simply changing the access method for the view from “by role” to “by permission” made the test pass, and prevented a potential security flaw in the code.&lt;/p&gt;

&lt;p&gt;(Another reason to avoid “by role” access in views is that User 1 often does not have the role required, and it is often disconcerting to be user 1 and have “access denied” to a view.)&lt;/p&gt;

&lt;p&gt;So in conclusion, Simpletest is great when it works as expected and when you understand what it does, but when you don’t, it is always good to know a few techniques for further investigation.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;I have been using Simpletest on Drupal 7 for several years, and, used well, it can greatly enhance the quality of your code. I like to practice &lt;a href=&quot;http://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;test-driven development&lt;/a&gt;: writing a failing test first, then run it multiple times, each time tweaking the code, until the test passes.&lt;/p&gt;

&lt;p&gt;Simpletest works by spawning a completely new Drupal site (ignoring your current database), running tests, and destroying the database. Sometimes, a test will fail and you’re not quite sure why. Here are two tips to help you debug why your tests are failing:&lt;/p&gt;

&lt;h2 id=&quot;tip-1-debug&quot;&gt;Tip #1: debug()&lt;/h2&gt;

&lt;p&gt;The Drupal &lt;a href=&quot;https://api.drupal.org/api/drupal/includes!common.inc/function/debug/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debug()&lt;/code&gt; function&lt;/a&gt; can be placed &lt;em&gt;anywhere in your test or your source code&lt;/em&gt;, and the result will appear on the test  results page in the GUI.&lt;/p&gt;

&lt;p&gt;For example, if when you are playing around with the dev version of your site, things work fine, but in the test, a specific node contains invalid data, you can add this line anywhere in your test or source code which is being called during your test:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
debug($node);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will provide formatted output of your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$node&lt;/code&gt; variable, alongside your test results.&lt;/p&gt;

&lt;h2 id=&quot;tip-2-die&quot;&gt;Tip #2: die()&lt;/h2&gt;

&lt;p&gt;Sometimes the temporary test environment’s behaviour seems to make no sense. And it can be frustrating to not be able to simply log into it and play around with it, because it is destroyed after the test is over.&lt;/p&gt;

&lt;p&gt;To understand this technique, here is quick primer on how Simpletest works:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;In Drupal 7, running a test requires a host site and database. This is basically an installed Drupal site with Simpletest enabled, and your module somewhere in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;modules&lt;/code&gt; directory (the module you are testing does not have to be enabled).&lt;/li&gt;
  &lt;li&gt;When you run a test, Simpletest creates a brand-new installation of Drupal using a special prefix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simpletest123456&lt;/code&gt; where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;123456&lt;/code&gt; is a random number. This allows Simpletest to have an isolated environment where to run tests, but on the same database and with the same credentials as the host.&lt;/li&gt;
  &lt;li&gt;When your test does something, like call a function, or load a page with, for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$this-&amp;gt;drupalGet(&apos;user&apos;)&lt;/code&gt;, the host environment is ignored and temporary environment (which uses the prefixed database tables) is used. In the previous example, the test loads the “user” page using a real HTTP calls. Simpletest knows to use the temporary environment because the call is made using a specially-crafted user agent.&lt;/li&gt;
  &lt;li&gt;When the test is over, all tables with the prefix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simpletest123456&lt;/code&gt; are destroyed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have ever tried to run a test on a host environment which already contains a prefix, you will understand why you can get “table name too long” errors in certain cases: Simpletest is trying to add a prefix to another prefix. That’s one reason to avoid prefixes when you can, but I digress.&lt;/p&gt;

&lt;p&gt;Now you can try this: somewhere in your test code, add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;die()&lt;/code&gt;, this will kill Simpletest, leaving the temporary database intact.&lt;/p&gt;

&lt;p&gt;Here is an example: a colleague recently was testing a feature which exported a view. In the dev environment, the view was available to users with the role &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manager&lt;/code&gt;, as was expected. However when the test logged in as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manager&lt;/code&gt; user and attempted to access the view, the result was an “Access denied” page.&lt;/p&gt;

&lt;p&gt;Because we couldn’t easily figure it out, I suggested adding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;die()&lt;/code&gt; to play around in the environment:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$this-&amp;gt;drupalLogin($manager);
$this-&amp;gt;drupalGet(&apos;inventory&apos;);
die();
$this-&amp;gt;assertNoText(&apos;denied&apos;, &apos;A manager accessing the inventory page does not see &quot;access denied&quot;&apos;);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, when the test was run, we could:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;wait for it to crash,&lt;/li&gt;
  &lt;li&gt;then examine our database to figure out which prefix the test was using,&lt;/li&gt;
  &lt;li&gt;change the database prefix in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/default/settings.php&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;&apos;&lt;/code&gt; to (for example) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;simpletest73845&apos;&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush uli&lt;/code&gt; to get a one-time login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, it was easier to debug the source of the problem by visiting the views configuration for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inventory&lt;/code&gt;: it turns out that features exports views with access by role using the role ID, not the role name (the role ID can be different for each environment). Simply changing the access method for the view from “by role” to “by permission” made the test pass, and prevented a potential security flaw in the code.&lt;/p&gt;

&lt;p&gt;(Another reason to avoid “by role” access in views is that User 1 often does not have the role required, and it is often disconcerting to be user 1 and have “access denied” to a view.)&lt;/p&gt;

&lt;p&gt;So in conclusion, Simpletest is great when it works as expected and when you understand what it does, but when you don’t, it is always good to know a few techniques for further investigation.&lt;/p&gt;
</description>
        
        <pubDate>Fri, 06 Feb 2015 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/88/two-tips-debugging-simpletest-tests/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/88/two-tips-debugging-simpletest-tests/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Multiple git remotes, the --depth parameter and repo size</title>
        <description>&lt;p&gt;When building a Drupal 7 site, one oft-used technique is to keep the entire Drupal root under git (for Drupal 8 sites, I favor &lt;a href=&quot;http://blog.dcycle.com/blog/68&quot;&gt;having the Drupal root one level up&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Starting a new project can be done by downloading an unversioned copy of D7, and initializing a git repo, like this:&lt;/p&gt;

&lt;h2 id=&quot;approach-1&quot;&gt;Approach #1&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush dl
cd drupal*
git init
git add .
git commit -am &apos;initial project commit&apos;
git remote add origin ssh://me@mygit.example.com/myproject
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another trick I learned from my colleagues at the Linux Foundation is to get Drupal via git and have two origins, like this:&lt;/p&gt;

&lt;h2 id=&quot;approach-2&quot;&gt;Approach #2&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone --branch 7.x http://git.drupal.org/project/drupal.git drupal
cd drupal
git remote rename origin drupal
git remote add origin ssh://me@mygit.example.com/myproject
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This second approach lets you push changes to your own repo, and pull changes from the Drupal git repo. This has the advantage of keeping track of Drupal project commits, and your own project commits, in a unified git history.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git push origin 7.x
git pull drupal 7.x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you are tight for space though, there might be one inconvenience: Approach #2 keeps track of the &lt;em&gt;entire Drupal 7.x commit history&lt;/em&gt;, for example we are now tracking in our own repo commit e829881 by natrak, on June 2, 2000:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git log |grep e829881 --after-context=4
commit e8298816587f79e090cb6e78ea17b00fae705deb
Author: natrak &amp;lt;&amp;gt;
Date:   Fri Jun 2 18:43:11 2000 +0000

    CVS drives me nuts *G*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All of this information takes disk space: Approach #2 takes 156Mb, vs. 23Mb for approach #1. This may add up if you are working on several projects, and especially if for each project you have several environments for feature branches. If you have a continuous integration server tracking multiple projects and spawning new environments for each feature branch, several gigs of disk space can be used.&lt;/p&gt;

&lt;p&gt;If you want to streamline the size of your git repos, you might want to try the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--depth&lt;/code&gt; option of git clone, like this:&lt;/p&gt;

&lt;h2 id=&quot;approach-3&quot;&gt;Approach #3&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone --branch 7.x --depth 1 http://git.drupal.org/project/drupal.git drupal
cd drupal
git remote rename origin drupal
git remote add origin ssh://me@mygit.example.com/myproject
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Adding the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--depth&lt;/code&gt; parameter here reduces the initial size of your repo to 18Mb in my test, which interestingly is even less than approach #1. Even though your repo is now linked to the Drupal git repo, by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt; you will see that the entire history is not being stored.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;When building a Drupal 7 site, one oft-used technique is to keep the entire Drupal root under git (for Drupal 8 sites, I favor &lt;a href=&quot;http://blog.dcycle.com/blog/68&quot;&gt;having the Drupal root one level up&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Starting a new project can be done by downloading an unversioned copy of D7, and initializing a git repo, like this:&lt;/p&gt;

&lt;h2 id=&quot;approach-1&quot;&gt;Approach #1&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush dl
cd drupal*
git init
git add .
git commit -am &apos;initial project commit&apos;
git remote add origin ssh://me@mygit.example.com/myproject
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another trick I learned from my colleagues at the Linux Foundation is to get Drupal via git and have two origins, like this:&lt;/p&gt;

&lt;h2 id=&quot;approach-2&quot;&gt;Approach #2&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone --branch 7.x http://git.drupal.org/project/drupal.git drupal
cd drupal
git remote rename origin drupal
git remote add origin ssh://me@mygit.example.com/myproject
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This second approach lets you push changes to your own repo, and pull changes from the Drupal git repo. This has the advantage of keeping track of Drupal project commits, and your own project commits, in a unified git history.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git push origin 7.x
git pull drupal 7.x
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you are tight for space though, there might be one inconvenience: Approach #2 keeps track of the &lt;em&gt;entire Drupal 7.x commit history&lt;/em&gt;, for example we are now tracking in our own repo commit e829881 by natrak, on June 2, 2000:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git log |grep e829881 --after-context=4
commit e8298816587f79e090cb6e78ea17b00fae705deb
Author: natrak &amp;lt;&amp;gt;
Date:   Fri Jun 2 18:43:11 2000 +0000

    CVS drives me nuts *G*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All of this information takes disk space: Approach #2 takes 156Mb, vs. 23Mb for approach #1. This may add up if you are working on several projects, and especially if for each project you have several environments for feature branches. If you have a continuous integration server tracking multiple projects and spawning new environments for each feature branch, several gigs of disk space can be used.&lt;/p&gt;

&lt;p&gt;If you want to streamline the size of your git repos, you might want to try the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--depth&lt;/code&gt; option of git clone, like this:&lt;/p&gt;

&lt;h2 id=&quot;approach-3&quot;&gt;Approach #3&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone --branch 7.x --depth 1 http://git.drupal.org/project/drupal.git drupal
cd drupal
git remote rename origin drupal
git remote add origin ssh://me@mygit.example.com/myproject
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Adding the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--depth&lt;/code&gt; parameter here reduces the initial size of your repo to 18Mb in my test, which interestingly is even less than approach #1. Even though your repo is now linked to the Drupal git repo, by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git log&lt;/code&gt; you will see that the entire history is not being stored.&lt;/p&gt;
</description>
        
        <pubDate>Tue, 20 Jan 2015 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/87/multiple-git-remotes-depth-parameter-and-repo-size/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/87/multiple-git-remotes-depth-parameter-and-repo-size/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>What is content? What is configuration?</title>
        <description>&lt;p&gt;What is content? What is configuration? At first glance, the question seems simple, almost quaint, the kind one finds oneself patiently answering for the benefit of Drupal novices: content is usually information like nodes and taxonomy terms, while  content types, views and taxonomy vocabularies are usually configuration.&lt;/p&gt;

&lt;p&gt;Content lives in the database of each environment, we say, while configuration is exportable via Features or other mechanisms and should live in the Git repo (this has been called code-driven development).&lt;/p&gt;

&lt;p&gt;Still, a definition of content and configuration is naggingly elusive: why “usually”? Why are there so many edge cases? We’re engineers, we need precision! I often feel like I’m trying to define what a bird is: every child knows what a bird is, but it’s hard to define it. Ostriches can’t fly; platypuses lay eggs but aren’t birds.&lt;/p&gt;

&lt;h2 id=&quot;why-the-distinction&quot;&gt;Why the distinction?&lt;/h2&gt;

&lt;p&gt;I recently saw an interesting comment titled “&lt;a href=&quot;http://agaric.com/comment/1499#comment-1499&quot;&gt;A heretic speaks&lt;/a&gt;” on a blog post about code-driven development. It sums up some of the uneasiness about the place of configuration in Drupal: “Drupal was built primarily with site builders in mind, and this is one reason [configuration] is in the database”.&lt;/p&gt;

&lt;p&gt;In effect, the primary distinction in Drupal is between code (Drupal core and config), and the database, which contains content types, nodes, and everything else.&lt;/p&gt;

&lt;p&gt;As more complex sites were being built, a new distinction had to be made between two types of information in the database: configuration and content. This was required to allow development in a dev-stage-production workflow where &lt;em&gt;features&lt;/em&gt; being developed outside of a production site could be deployed to production without squashing the database (and existing comments, nodes, &lt;em&gt;and the like&lt;/em&gt;). We needed to move those features into code and we called them “configuration”.&lt;/p&gt;

&lt;p&gt;Thus the &lt;a href=&quot;https://www.drupal.org/project/features&quot;&gt;features&lt;/a&gt; module was born, allowing views, content types, and vocabularies (but not nodes and taxonomy terms) to be developed outside of the database, and then deployed into production.&lt;/p&gt;

&lt;p&gt;Drupal 8’s &lt;a href=&quot;http://blog.dcycle.com/blog/68/approach-code-driven-development-drupal-8&quot;&gt;config management system&lt;/a&gt; takes that one step further by providing a mature, central API to deal with this.&lt;/p&gt;

&lt;h2 id=&quot;the-devil-is-in-the-details&quot;&gt;The devil is in the details&lt;/h2&gt;

&lt;p&gt;This is all fine and good, but edge cases soon begin to arise:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What about an “About us” page? It’s a menu item (deployable) linking to a node (content). Is it config? Is it content?&lt;/li&gt;
  &lt;li&gt;What about a “Social media” menu and its menu items? We want a Facebook link to be deployable, but we don’t want to hard-code the actual link to our client’s Facebook page (which &lt;em&gt;feels&lt;/em&gt; like content) – we probably don’t even know what that link is during development.&lt;/li&gt;
  &lt;li&gt;What about a block whose placement is known, but whose content is not? Is this content? Is it configuration?&lt;/li&gt;
  &lt;li&gt;What about a view which references a taxonomy term id in a hard-coded filter. We can export the view, but the taxonomy term has an incremental ID ans is not guaranteed to work on all environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wrong answer to any of these questions can lead to a misguided development approach which will come back to haunt you afterward. You might wind up using &lt;a href=&quot;http://blog.dcycle.com/blog/50/do-not-use-incremental-ids-your-code&quot;&gt;incremental IDs in your code&lt;/a&gt; or deploying something as configuration which is, in fact, content.&lt;/p&gt;

&lt;h2 id=&quot;defining-our-terms&quot;&gt;Defining our terms&lt;/h2&gt;

&lt;p&gt;At the risk of irking you, dear reader, I will suggest doing away with the terms “content” and “configuration” for our purposes: they are just too vague. Because we want a formal definition with no edge cases, I propose that we use these terms instead (we’ll look at each in detail a bit further on):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Code&lt;/strong&gt;: this is what our deliverable is for a given project. It should be testable, versioned, and deployable to any number of environments.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data&lt;/strong&gt;: this is whatever is potentially different on each environment to which our code is deployed. One example is comments: On a dev environment, we might generate thousands of dummy comments for theming purposes, but on prod there might be a few dozen only.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Placeholder content&lt;/strong&gt;: this is any data which should be created as part of the installation process, meant to be changed later on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;code&quot;&gt;Code&lt;/h2&gt;

&lt;p&gt;This is what our deliverable is &lt;em&gt;for a given project&lt;/em&gt;. This is important. There is no single answer. Let’s take the following examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;If I am a contributor to the &lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views&lt;/a&gt; contrib project, my &lt;em&gt;deliverable&lt;/em&gt; is &lt;em&gt;a system which allows users to create views in the database&lt;/em&gt;. In this case I will not export many particular views.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For another project, my deliverable may be &lt;em&gt;a website which contains a set number of lists (views)&lt;/em&gt;. In this case I may use &lt;a href=&quot;https://www.drupal.org/project/features&quot;&gt;features&lt;/a&gt; (D7) or &lt;a href=&quot;http://blog.dcycle.com/blog/68/approach-code-driven-development-drupal-8&quot;&gt;config management&lt;/a&gt; (D8) to export all the views my client asked for. Furthermore, I may enable views_ui (the Views User interface) only on my development box, and disable it on production.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For a third project, my deliverable may a website with a number of set views, &lt;em&gt;plus the ability for the client to add new ones&lt;/em&gt;. In this only certain views will be in code, and I will enable the views UI as a dependency of my &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;. The views my client creates on production will be data.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;data&quot;&gt;Data&lt;/h2&gt;

&lt;p&gt;A few years ago, I took a step back from my day-to-day Drupal work and thought about what my main pain points were and how to do away with them. After consulting with colleagues, looking at bugs which took longest to fix, and looking at major sources of regressions, I realized that the one thing all major pain points had in common were our deployment techniques.&lt;/p&gt;

&lt;p&gt;It struck me that &lt;a href=&quot;http://blog.dcycle.com/blog/48/do-not-clone-database&quot;&gt;cloning the database from production to development was wrong&lt;/a&gt;. Relying on production data to do development is sloppy and will cause problems. It is better to invest in &lt;a href=&quot;https://www.drupal.org/project/realistic_dummy_content&quot;&gt;realistic dummy content&lt;/a&gt; and a good &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;, allowing the standardized deployment of an environment in a few minutes from any commit.&lt;/p&gt;

&lt;p&gt;Once we remove data from the development equation in this way, it is easier to define what data is: anything which can differ from one environment to the next without overriding a feature.&lt;/p&gt;

&lt;p&gt;Furthermore, I like to think of &lt;em&gt;production&lt;/em&gt; as just another environment, there is nothing special about it.&lt;/p&gt;

&lt;p&gt;A new view or content type created on production outside of our development cycle resides on the database, is never used during the course of development, and is therefore data.&lt;/p&gt;

&lt;p&gt;Nodes and taxonomy terms are data.&lt;/p&gt;

&lt;p&gt;What about a view which is deployed through features and later changed on another environment? That’s a tough one, I’ll get to it (See &lt;em&gt;Overriden features&lt;/em&gt;, below).&lt;/p&gt;

&lt;h2 id=&quot;placeholder-content&quot;&gt;Placeholder content&lt;/h2&gt;

&lt;p&gt;Let’s get back to our “About us” page. Three components are involved here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The menu which contains the “About us” menu item. These types of menus are generally deployable, so let’s call them code.&lt;/li&gt;
  &lt;li&gt;The “About us” node itself which has an incremental &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nid&lt;/code&gt; which can be different on each environment. On some environments it might not even exist.&lt;/li&gt;
  &lt;li&gt;The “About us” menu item, which should link to the node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember: we are not cloning the production database, so the “About us” does not exist anywhere. For situations such as this, I will suggest the use of Placeholder content.&lt;/p&gt;

&lt;p&gt;For sake of argument, let’s define our &lt;em&gt;deliverable&lt;/em&gt; for this sample project as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;Define an _About us_ page which is modifiable&quot;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We might be tempted to figure out a way to assign a unique ID to our “About us” node to make it deployable, and devise all kinds of techniques to make sure it cannot be deleted or overridden.&lt;/p&gt;

&lt;p&gt;I have an approach which I consider more logical for these situations:&lt;/p&gt;

&lt;p&gt;First, in my &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt;, create the node and the menu item, bypassing features entirely. Something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function mysite_deploy_update_7023() {
  $node = new stdClass();
  $node-&amp;gt;title = &apos;About us&apos;;
  $node-&amp;gt;body[LANGUAGE_NONE][0][&apos;format&apos;] = &apos;filtered_html&apos;;
  $node-&amp;gt;body[LANGUAGE_NONE][0][&apos;value&apos;] = &apos;Lorem ipsum...&apos;;
  $node-&amp;gt;type = &apos;page&apos;;
  node_object_prepare($node);
  $node-&amp;gt;uid = 1;
  $node-&amp;gt;status = 1;
  $node-&amp;gt;promote = 0;
  node_save($node);

  $menu_item = array(
    &apos;link_path&apos; =&amp;gt; &apos;node/&apos; . $node-&amp;gt;nid,
    &apos;link_title&apos; =&amp;gt; &apos;About us&apos;,
    &apos;menu_name&apos; =&amp;gt; &apos;my-existing-menu-exported-via-features&apos;,
  );

  menu_link_save($item);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you wish, you can also implement &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_requirements/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_requirements()&lt;/code&gt;&lt;/a&gt; in your custom module, to check that the About us page has not been accidentally deleted, that the menu item exists and points to a valid path.&lt;/p&gt;

&lt;p&gt;What are the advantages of placeholder content?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It is deployable in a standard manner: any environment can simply run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; and the placeholder content will be deployed.&lt;/li&gt;
  &lt;li&gt;It can be changed without rendering your features (D7) or configuration (D8) overriden. This is a good thing: if our incremental deployment script calls &lt;a href=&quot;http://drupalcontrib.org/api/drupal/contributions!features!features.module/function/features_revert/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;features_revert()&lt;/code&gt;&lt;/a&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush fra -y&lt;/code&gt; (D7) or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim -y&lt;/code&gt; (D8), all changes to features are deleted. We do not want changes made to our placeholder content to be deleted.&lt;/li&gt;
  &lt;li&gt;It can be easily tested. All we need to do is make sure our site deployment module’s &lt;a href=&quot;http://blog.dcycle.com/blog/65/basic-install-file-deployment-module&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_install()&lt;/code&gt; calls all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt;s&lt;/a&gt;; then we can enable our site deployment module &lt;a href=&quot;http://blog.dcycle.com/blog/30/basic-test&quot;&gt;within our simpletest&lt;/a&gt;, and run any tests we want against a known good starting point.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;overriden-features&quot;&gt;Overriden features&lt;/h2&gt;

&lt;p&gt;Although it is easy to override features on production, I would not recommend it. It is important to define with your client and your team what is code and what is data. Again, this depends on the project.&lt;/p&gt;

&lt;p&gt;When a feature gets overridden, it is a symptom that someone does not understand the process. Here are a few ways to mitigate this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure your features are reverted (D7) or your configuration is imported (D8) as part of your deployment process, and &lt;a href=&quot;http://blog.dcycle.com/blog/46/continuous-deployment-drupal-style&quot;&gt;automate that process&lt;/a&gt; with a continuous integration server. That way, if anyone overrides a feature on a production, it won’t stay overridden long.&lt;/li&gt;
  &lt;li&gt;Limit administrator permissions so that only user 1 can override features (this can be more trouble than it’s worth though).&lt;/li&gt;
  &lt;li&gt;Implement &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_requirements/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_requirements()&lt;/code&gt;&lt;/a&gt; to check for overridden features, warning you on the environment’s dashboard if a feature has been overridden.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;some-edge-cases&quot;&gt;Some edge cases&lt;/h2&gt;

&lt;p&gt;Now, with our more rigorous approach, how do our edge cases fare?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Social media menu and items&lt;/strong&gt;: Our deliverable here is the existence of a social media menu with two items (twitter and facebook), but whose links can be changed at any time on production without triggering an overridden feature. For this I would use placeholder content. Still, we need to theme each button separately, and our css does not know the incremental IDs of the menu items we are creating. I have successfully used the &lt;a href=&quot;https://www.drupal.org/project/menu_attributes&quot;&gt;menu attributes&lt;/a&gt; module to associate classes to menu items, allowing easy theming. Here is an example, assuming &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;menu_attributes&lt;/code&gt; exists and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;menu-social&lt;/code&gt; has been exported as a feature.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Add facebook and twitter menu items
 */
function mysite_deploy_update_7117() {
  $item = array(
    &apos;link_path&apos; =&amp;gt; &apos;http://twitter.com&apos;,
    &apos;link_title&apos; =&amp;gt; &apos;Twitter&apos;,
    &apos;menu_name&apos; =&amp;gt; &apos;menu-social&apos;,
    &apos;options&apos; =&amp;gt; array(
      &apos;attributes&apos; =&amp;gt; array(
        &apos;class&apos; =&amp;gt; &apos;twitter&apos;,
      )
    )
  );
  menu_link_save($item);
  $item = array(
    &apos;link_path&apos; =&amp;gt; &apos;http://facebook.com&apos;,
    &apos;link_title&apos; =&amp;gt; &apos;Facebook&apos;,
    &apos;menu_name&apos; =&amp;gt; &apos;menu-social&apos;,
    &apos;options&apos; =&amp;gt; array(
      &apos;attributes&apos; =&amp;gt; array(
        &apos;class&apos; =&amp;gt; &apos;facebook&apos;,
      )
    )
  );
  menu_link_save($item);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above code creates the menu items linking to Facebook and Twitter home pages, so that content editors can put in the correct links directly on production when they have them.&lt;/p&gt;

&lt;p&gt;Placeholder content is just like regular data but it’s created as part of the deployment process, as a service to the webmaster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A block whose placement is known, but whose content is not&lt;/strong&gt;. It may be tempting to use the &lt;a href=&quot;http://drupal.org/project/box&quot;&gt;box&lt;/a&gt; module which makes blocks exportable with feature. But in this case the block is more like placeholder content, so it should be deployed outside of features. And if you create your block programmatically, its id is incremental and it cannot be deployed with &lt;a href=&quot;https://www.drupal.org/project/context&quot;&gt;context&lt;/a&gt;, but should be placed in a region directly, again, programmatically in a hook_update_N().&lt;/p&gt;

&lt;p&gt;Another approach here is to create a content type and a view with a block display, fetching the last published node of that content type and displaying it at the right place. If you go that route (which seems a bit overengineered to me), you can then place your block with the context module and export it via features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A view which references a taxonomy term id in its filter&lt;/strong&gt;: If a view requires access to a taxonomy term nid, then perhaps taxonomy is the wrong tool here. Taxonomy terms are data, they can be deleted, their names can be changed. It is not a good idea for a view to reference a specific taxonomy term. (Your view can use taxonomy terms for contextual filters without a problem, but we don’t want to hard-code a specific term in a non-contextual filter – See &lt;a href=&quot;https://github.com/alberto56/dcyclesite/issues/3&quot;&gt;this issue&lt;/a&gt; for an example of how I learned this the hard way, I’ll get around to fixing that soon…).&lt;/p&gt;

&lt;p&gt;For this problem I would suggest rethinking our use of a taxonomy term. Rather I would define a select field with a set number of options (with defined keys and values). These are deployable and guaranteed to not change without triggering a features override. Thus, our views can safely use them. If you are implementing this change on an existing site, you will need to update all nodes from the old to the new technique in a hook_update_N() – and probably add an automated test to make sure you’re updating the data correctly. This is one more reason to think things through properly at the onset of your project, not midway through.&lt;/p&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h2&gt;

&lt;p&gt;Content and configuration are hard to define, I prefer the following definitions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Code&lt;/strong&gt;: deployable, deliverable, versioned, tested piece of software.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data&lt;/strong&gt;: anything which can differ from one environment to the next.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Placeholder content&lt;/strong&gt;: any data which should be created as part of the deployment process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my experience, what fits in each category &lt;em&gt;depends on each project&lt;/em&gt;. Defining these with your team as part of your sprint planning will allow you create a system with less edge cases.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;What is content? What is configuration? At first glance, the question seems simple, almost quaint, the kind one finds oneself patiently answering for the benefit of Drupal novices: content is usually information like nodes and taxonomy terms, while  content types, views and taxonomy vocabularies are usually configuration.&lt;/p&gt;

&lt;p&gt;Content lives in the database of each environment, we say, while configuration is exportable via Features or other mechanisms and should live in the Git repo (this has been called code-driven development).&lt;/p&gt;

&lt;p&gt;Still, a definition of content and configuration is naggingly elusive: why “usually”? Why are there so many edge cases? We’re engineers, we need precision! I often feel like I’m trying to define what a bird is: every child knows what a bird is, but it’s hard to define it. Ostriches can’t fly; platypuses lay eggs but aren’t birds.&lt;/p&gt;

&lt;h2 id=&quot;why-the-distinction&quot;&gt;Why the distinction?&lt;/h2&gt;

&lt;p&gt;I recently saw an interesting comment titled “&lt;a href=&quot;http://agaric.com/comment/1499#comment-1499&quot;&gt;A heretic speaks&lt;/a&gt;” on a blog post about code-driven development. It sums up some of the uneasiness about the place of configuration in Drupal: “Drupal was built primarily with site builders in mind, and this is one reason [configuration] is in the database”.&lt;/p&gt;

&lt;p&gt;In effect, the primary distinction in Drupal is between code (Drupal core and config), and the database, which contains content types, nodes, and everything else.&lt;/p&gt;

&lt;p&gt;As more complex sites were being built, a new distinction had to be made between two types of information in the database: configuration and content. This was required to allow development in a dev-stage-production workflow where &lt;em&gt;features&lt;/em&gt; being developed outside of a production site could be deployed to production without squashing the database (and existing comments, nodes, &lt;em&gt;and the like&lt;/em&gt;). We needed to move those features into code and we called them “configuration”.&lt;/p&gt;

&lt;p&gt;Thus the &lt;a href=&quot;https://www.drupal.org/project/features&quot;&gt;features&lt;/a&gt; module was born, allowing views, content types, and vocabularies (but not nodes and taxonomy terms) to be developed outside of the database, and then deployed into production.&lt;/p&gt;

&lt;p&gt;Drupal 8’s &lt;a href=&quot;http://blog.dcycle.com/blog/68/approach-code-driven-development-drupal-8&quot;&gt;config management system&lt;/a&gt; takes that one step further by providing a mature, central API to deal with this.&lt;/p&gt;

&lt;h2 id=&quot;the-devil-is-in-the-details&quot;&gt;The devil is in the details&lt;/h2&gt;

&lt;p&gt;This is all fine and good, but edge cases soon begin to arise:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What about an “About us” page? It’s a menu item (deployable) linking to a node (content). Is it config? Is it content?&lt;/li&gt;
  &lt;li&gt;What about a “Social media” menu and its menu items? We want a Facebook link to be deployable, but we don’t want to hard-code the actual link to our client’s Facebook page (which &lt;em&gt;feels&lt;/em&gt; like content) – we probably don’t even know what that link is during development.&lt;/li&gt;
  &lt;li&gt;What about a block whose placement is known, but whose content is not? Is this content? Is it configuration?&lt;/li&gt;
  &lt;li&gt;What about a view which references a taxonomy term id in a hard-coded filter. We can export the view, but the taxonomy term has an incremental ID ans is not guaranteed to work on all environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wrong answer to any of these questions can lead to a misguided development approach which will come back to haunt you afterward. You might wind up using &lt;a href=&quot;http://blog.dcycle.com/blog/50/do-not-use-incremental-ids-your-code&quot;&gt;incremental IDs in your code&lt;/a&gt; or deploying something as configuration which is, in fact, content.&lt;/p&gt;

&lt;h2 id=&quot;defining-our-terms&quot;&gt;Defining our terms&lt;/h2&gt;

&lt;p&gt;At the risk of irking you, dear reader, I will suggest doing away with the terms “content” and “configuration” for our purposes: they are just too vague. Because we want a formal definition with no edge cases, I propose that we use these terms instead (we’ll look at each in detail a bit further on):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Code&lt;/strong&gt;: this is what our deliverable is for a given project. It should be testable, versioned, and deployable to any number of environments.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data&lt;/strong&gt;: this is whatever is potentially different on each environment to which our code is deployed. One example is comments: On a dev environment, we might generate thousands of dummy comments for theming purposes, but on prod there might be a few dozen only.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Placeholder content&lt;/strong&gt;: this is any data which should be created as part of the installation process, meant to be changed later on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;code&quot;&gt;Code&lt;/h2&gt;

&lt;p&gt;This is what our deliverable is &lt;em&gt;for a given project&lt;/em&gt;. This is important. There is no single answer. Let’s take the following examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;If I am a contributor to the &lt;a href=&quot;https://www.drupal.org/project/views&quot;&gt;Views&lt;/a&gt; contrib project, my &lt;em&gt;deliverable&lt;/em&gt; is &lt;em&gt;a system which allows users to create views in the database&lt;/em&gt;. In this case I will not export many particular views.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For another project, my deliverable may be &lt;em&gt;a website which contains a set number of lists (views)&lt;/em&gt;. In this case I may use &lt;a href=&quot;https://www.drupal.org/project/features&quot;&gt;features&lt;/a&gt; (D7) or &lt;a href=&quot;http://blog.dcycle.com/blog/68/approach-code-driven-development-drupal-8&quot;&gt;config management&lt;/a&gt; (D8) to export all the views my client asked for. Furthermore, I may enable views_ui (the Views User interface) only on my development box, and disable it on production.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For a third project, my deliverable may a website with a number of set views, &lt;em&gt;plus the ability for the client to add new ones&lt;/em&gt;. In this only certain views will be in code, and I will enable the views UI as a dependency of my &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;. The views my client creates on production will be data.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;data&quot;&gt;Data&lt;/h2&gt;

&lt;p&gt;A few years ago, I took a step back from my day-to-day Drupal work and thought about what my main pain points were and how to do away with them. After consulting with colleagues, looking at bugs which took longest to fix, and looking at major sources of regressions, I realized that the one thing all major pain points had in common were our deployment techniques.&lt;/p&gt;

&lt;p&gt;It struck me that &lt;a href=&quot;http://blog.dcycle.com/blog/48/do-not-clone-database&quot;&gt;cloning the database from production to development was wrong&lt;/a&gt;. Relying on production data to do development is sloppy and will cause problems. It is better to invest in &lt;a href=&quot;https://www.drupal.org/project/realistic_dummy_content&quot;&gt;realistic dummy content&lt;/a&gt; and a good &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;, allowing the standardized deployment of an environment in a few minutes from any commit.&lt;/p&gt;

&lt;p&gt;Once we remove data from the development equation in this way, it is easier to define what data is: anything which can differ from one environment to the next without overriding a feature.&lt;/p&gt;

&lt;p&gt;Furthermore, I like to think of &lt;em&gt;production&lt;/em&gt; as just another environment, there is nothing special about it.&lt;/p&gt;

&lt;p&gt;A new view or content type created on production outside of our development cycle resides on the database, is never used during the course of development, and is therefore data.&lt;/p&gt;

&lt;p&gt;Nodes and taxonomy terms are data.&lt;/p&gt;

&lt;p&gt;What about a view which is deployed through features and later changed on another environment? That’s a tough one, I’ll get to it (See &lt;em&gt;Overriden features&lt;/em&gt;, below).&lt;/p&gt;

&lt;h2 id=&quot;placeholder-content&quot;&gt;Placeholder content&lt;/h2&gt;

&lt;p&gt;Let’s get back to our “About us” page. Three components are involved here:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The menu which contains the “About us” menu item. These types of menus are generally deployable, so let’s call them code.&lt;/li&gt;
  &lt;li&gt;The “About us” node itself which has an incremental &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nid&lt;/code&gt; which can be different on each environment. On some environments it might not even exist.&lt;/li&gt;
  &lt;li&gt;The “About us” menu item, which should link to the node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember: we are not cloning the production database, so the “About us” does not exist anywhere. For situations such as this, I will suggest the use of Placeholder content.&lt;/p&gt;

&lt;p&gt;For sake of argument, let’s define our &lt;em&gt;deliverable&lt;/em&gt; for this sample project as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&quot;Define an _About us_ page which is modifiable&quot;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We might be tempted to figure out a way to assign a unique ID to our “About us” node to make it deployable, and devise all kinds of techniques to make sure it cannot be deleted or overridden.&lt;/p&gt;

&lt;p&gt;I have an approach which I consider more logical for these situations:&lt;/p&gt;

&lt;p&gt;First, in my &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt;, create the node and the menu item, bypassing features entirely. Something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;function mysite_deploy_update_7023() {
  $node = new stdClass();
  $node-&amp;gt;title = &apos;About us&apos;;
  $node-&amp;gt;body[LANGUAGE_NONE][0][&apos;format&apos;] = &apos;filtered_html&apos;;
  $node-&amp;gt;body[LANGUAGE_NONE][0][&apos;value&apos;] = &apos;Lorem ipsum...&apos;;
  $node-&amp;gt;type = &apos;page&apos;;
  node_object_prepare($node);
  $node-&amp;gt;uid = 1;
  $node-&amp;gt;status = 1;
  $node-&amp;gt;promote = 0;
  node_save($node);

  $menu_item = array(
    &apos;link_path&apos; =&amp;gt; &apos;node/&apos; . $node-&amp;gt;nid,
    &apos;link_title&apos; =&amp;gt; &apos;About us&apos;,
    &apos;menu_name&apos; =&amp;gt; &apos;my-existing-menu-exported-via-features&apos;,
  );

  menu_link_save($item);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you wish, you can also implement &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_requirements/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_requirements()&lt;/code&gt;&lt;/a&gt; in your custom module, to check that the About us page has not been accidentally deleted, that the menu item exists and points to a valid path.&lt;/p&gt;

&lt;p&gt;What are the advantages of placeholder content?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It is deployable in a standard manner: any environment can simply run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; and the placeholder content will be deployed.&lt;/li&gt;
  &lt;li&gt;It can be changed without rendering your features (D7) or configuration (D8) overriden. This is a good thing: if our incremental deployment script calls &lt;a href=&quot;http://drupalcontrib.org/api/drupal/contributions!features!features.module/function/features_revert/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;features_revert()&lt;/code&gt;&lt;/a&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush fra -y&lt;/code&gt; (D7) or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cim -y&lt;/code&gt; (D8), all changes to features are deleted. We do not want changes made to our placeholder content to be deleted.&lt;/li&gt;
  &lt;li&gt;It can be easily tested. All we need to do is make sure our site deployment module’s &lt;a href=&quot;http://blog.dcycle.com/blog/65/basic-install-file-deployment-module&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_install()&lt;/code&gt; calls all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt;s&lt;/a&gt;; then we can enable our site deployment module &lt;a href=&quot;http://blog.dcycle.com/blog/30/basic-test&quot;&gt;within our simpletest&lt;/a&gt;, and run any tests we want against a known good starting point.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;overriden-features&quot;&gt;Overriden features&lt;/h2&gt;

&lt;p&gt;Although it is easy to override features on production, I would not recommend it. It is important to define with your client and your team what is code and what is data. Again, this depends on the project.&lt;/p&gt;

&lt;p&gt;When a feature gets overridden, it is a symptom that someone does not understand the process. Here are a few ways to mitigate this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure your features are reverted (D7) or your configuration is imported (D8) as part of your deployment process, and &lt;a href=&quot;http://blog.dcycle.com/blog/46/continuous-deployment-drupal-style&quot;&gt;automate that process&lt;/a&gt; with a continuous integration server. That way, if anyone overrides a feature on a production, it won’t stay overridden long.&lt;/li&gt;
  &lt;li&gt;Limit administrator permissions so that only user 1 can override features (this can be more trouble than it’s worth though).&lt;/li&gt;
  &lt;li&gt;Implement &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_requirements/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_requirements()&lt;/code&gt;&lt;/a&gt; to check for overridden features, warning you on the environment’s dashboard if a feature has been overridden.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;some-edge-cases&quot;&gt;Some edge cases&lt;/h2&gt;

&lt;p&gt;Now, with our more rigorous approach, how do our edge cases fare?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Social media menu and items&lt;/strong&gt;: Our deliverable here is the existence of a social media menu with two items (twitter and facebook), but whose links can be changed at any time on production without triggering an overridden feature. For this I would use placeholder content. Still, we need to theme each button separately, and our css does not know the incremental IDs of the menu items we are creating. I have successfully used the &lt;a href=&quot;https://www.drupal.org/project/menu_attributes&quot;&gt;menu attributes&lt;/a&gt; module to associate classes to menu items, allowing easy theming. Here is an example, assuming &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;menu_attributes&lt;/code&gt; exists and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;menu-social&lt;/code&gt; has been exported as a feature.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Add facebook and twitter menu items
 */
function mysite_deploy_update_7117() {
  $item = array(
    &apos;link_path&apos; =&amp;gt; &apos;http://twitter.com&apos;,
    &apos;link_title&apos; =&amp;gt; &apos;Twitter&apos;,
    &apos;menu_name&apos; =&amp;gt; &apos;menu-social&apos;,
    &apos;options&apos; =&amp;gt; array(
      &apos;attributes&apos; =&amp;gt; array(
        &apos;class&apos; =&amp;gt; &apos;twitter&apos;,
      )
    )
  );
  menu_link_save($item);
  $item = array(
    &apos;link_path&apos; =&amp;gt; &apos;http://facebook.com&apos;,
    &apos;link_title&apos; =&amp;gt; &apos;Facebook&apos;,
    &apos;menu_name&apos; =&amp;gt; &apos;menu-social&apos;,
    &apos;options&apos; =&amp;gt; array(
      &apos;attributes&apos; =&amp;gt; array(
        &apos;class&apos; =&amp;gt; &apos;facebook&apos;,
      )
    )
  );
  menu_link_save($item);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above code creates the menu items linking to Facebook and Twitter home pages, so that content editors can put in the correct links directly on production when they have them.&lt;/p&gt;

&lt;p&gt;Placeholder content is just like regular data but it’s created as part of the deployment process, as a service to the webmaster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A block whose placement is known, but whose content is not&lt;/strong&gt;. It may be tempting to use the &lt;a href=&quot;http://drupal.org/project/box&quot;&gt;box&lt;/a&gt; module which makes blocks exportable with feature. But in this case the block is more like placeholder content, so it should be deployed outside of features. And if you create your block programmatically, its id is incremental and it cannot be deployed with &lt;a href=&quot;https://www.drupal.org/project/context&quot;&gt;context&lt;/a&gt;, but should be placed in a region directly, again, programmatically in a hook_update_N().&lt;/p&gt;

&lt;p&gt;Another approach here is to create a content type and a view with a block display, fetching the last published node of that content type and displaying it at the right place. If you go that route (which seems a bit overengineered to me), you can then place your block with the context module and export it via features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A view which references a taxonomy term id in its filter&lt;/strong&gt;: If a view requires access to a taxonomy term nid, then perhaps taxonomy is the wrong tool here. Taxonomy terms are data, they can be deleted, their names can be changed. It is not a good idea for a view to reference a specific taxonomy term. (Your view can use taxonomy terms for contextual filters without a problem, but we don’t want to hard-code a specific term in a non-contextual filter – See &lt;a href=&quot;https://github.com/alberto56/dcyclesite/issues/3&quot;&gt;this issue&lt;/a&gt; for an example of how I learned this the hard way, I’ll get around to fixing that soon…).&lt;/p&gt;

&lt;p&gt;For this problem I would suggest rethinking our use of a taxonomy term. Rather I would define a select field with a set number of options (with defined keys and values). These are deployable and guaranteed to not change without triggering a features override. Thus, our views can safely use them. If you are implementing this change on an existing site, you will need to update all nodes from the old to the new technique in a hook_update_N() – and probably add an automated test to make sure you’re updating the data correctly. This is one more reason to think things through properly at the onset of your project, not midway through.&lt;/p&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h2&gt;

&lt;p&gt;Content and configuration are hard to define, I prefer the following definitions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Code&lt;/strong&gt;: deployable, deliverable, versioned, tested piece of software.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Data&lt;/strong&gt;: anything which can differ from one environment to the next.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Placeholder content&lt;/strong&gt;: any data which should be created as part of the deployment process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my experience, what fits in each category &lt;em&gt;depends on each project&lt;/em&gt;. Defining these with your team as part of your sprint planning will allow you create a system with less edge cases.&lt;/p&gt;
</description>
        
        <pubDate>Wed, 03 Dec 2014 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/83/what-content-what-configuration/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/83/what-content-what-configuration/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>An approach to code-driven development in Drupal 8</title>
        <description>&lt;h2 id=&quot;what-is-code-driven-development-and-why-is-it-done&quot;&gt;What is code-driven development and why is it done?&lt;/h2&gt;

&lt;p&gt;Code-driven development is the practice of placing all development in code. How can development not be in code?, you ask.&lt;/p&gt;

&lt;p&gt;In Drupal, what makes your site unique is often configuration which resides in the database: the current theme, active modules, module-specific configuration, content types, and so on.&lt;/p&gt;

&lt;p&gt;For the purpose of this article, our goal will be for all &lt;em&gt;configuration&lt;/em&gt; (the current theme, the content types, module-specific config, the active module list…) to be in &lt;em&gt;code&lt;/em&gt;, and only &lt;em&gt;content&lt;/em&gt; to be in the database. There are several advantages to this approach:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Because all our configuration is in code, we can package all of it into a single module, which we’ll call a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;. When enabled, this module should provide a fully workable site without any content.&lt;/li&gt;
  &lt;li&gt;When a site deployment module is combined with generated content, it becomes possible to create new instances of a website &lt;a href=&quot;http://blog.dcycle.com/blog/48/do-not-clone-database&quot;&gt;without cloning the database&lt;/a&gt;. &lt;a href=&quot;https://www.drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devel_generate&lt;/code&gt; module, and &lt;a href=&quot;https://www.drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt; can be used to create realistic dummy content. This makes on-ramping new developers easy and consistent.&lt;/li&gt;
  &lt;li&gt;Because unversioned databases are not required to be cloned to set up new environments, your continuous integration server can set up new instances of your site based on a known good starting point, making tests more robust.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;code-driven-development-for-drupal-7&quot;&gt;Code-driven development for Drupal 7&lt;/h2&gt;

&lt;p&gt;Before moving on to D8, let’s look at a typical D7 workflow: The technique I use for developing in Drupal 7 is making sure I have one or more &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;features&lt;/a&gt; with my content types, views, &lt;a href=&quot;http://drupal.org/project/context&quot;&gt;contexts&lt;/a&gt;, and so on; as well as a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt; which contains, in its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file, &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_update_N/7&quot;&gt;update hooks&lt;/a&gt; which revert my features when needed, enable new modules, and programmatically set configuration which can’t be exported via features. That way,&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;incrementally deploying sites is as simple as calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; (to run new update hooks).&lt;/li&gt;
  &lt;li&gt;deploying a site for the first time (or redeploying it from scratch) requires creating the database, enabling our site deployment module (which &lt;a href=&quot;http://blog.dcycle.com/blog/43/run-all-update-hooks-install-hook&quot;&gt;runs all or update hooks&lt;/a&gt;), and optionally generating dummy content if required. For example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush si -y &amp;amp;&amp;amp; drush en mysite_deploy -y &amp;amp;&amp;amp; drush en devel_generate &amp;amp;&amp;amp; drush generate-content 50&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have been using this technique for a few years on all my D7 projects and, in this article, I will explore how something similar can be done in D8.&lt;/p&gt;

&lt;h2 id=&quot;new-in-drupal-8-configuration-management&quot;&gt;New in Drupal 8: configuration management&lt;/h2&gt;

&lt;p&gt;If, like me, you are using &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;features&lt;/a&gt; exclusively to deploy websites (as opposed to using it to bundle generic functionality, for example having a “blog” feature, or a “calendar” feature you can add to any site), config management will replace features in D8. In D7, &lt;a href=&quot;https://www.drupal.org/project/context&quot;&gt;context&lt;/a&gt; is used to provide the ability to export block placement to features, and &lt;a href=&quot;https://www.drupal.org/project/strongarm&quot;&gt;strongarm&lt;/a&gt; exports variables. In D8, variables no longer exist, and block placement is now exportable. All of these modules are thus no longer needed.&lt;/p&gt;

&lt;p&gt;They are replaced by the concept of &lt;a href=&quot;https://www.drupal.org/documentation/administer/config&quot;&gt;configuration management&lt;/a&gt;, a central API for importing and exporting configuration as yml files.&lt;/p&gt;

&lt;h2 id=&quot;configuration-management-and-site-uuids&quot;&gt;Configuration management and site UUIDs&lt;/h2&gt;

&lt;p&gt;In Drupal 8, &lt;a href=&quot;https://www.drupal.org/node/2133325&quot;&gt;sites are now assigned a UUID on install&lt;/a&gt; and configuration can only be synchronized between sites having the same UUID. This is fine if the site has been cloned at some point from one environment to another, but as mentioned above, we are avoiding database cloning: we want it to be possible to install a brand new instance of a site at any time.&lt;/p&gt;

&lt;p&gt;We thus need a mechanism to assign the same UUID to all instances of our site, but still allow us to reinstall it without cloning the database.&lt;/p&gt;

&lt;p&gt;The solution I am using is to assign a site UUID in the site deployment module. Thus, in Drupal 8, my site deployment module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.module&lt;/code&gt; file looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @file
 * site deployment functions
 */
use Drupal\Core\Extension\InfoParser;

/**
 * Updates dependencies based on the site deployment&apos;s info file.
 *
 * If during the course of development, you add a dependency to your
 * site deployment module&apos;s .info file, increment the update hook
 * (see the .install module) and this function will be called, making
 * sure dependencies are enabled.
 */
function mysite_deploy_update_dependencies() {
  $parser = new InfoParser;
  $info_file = $parser-&amp;gt;parse(drupal_get_path(&apos;module&apos;, &apos;mysite_deploy&apos;) . &apos;/mysite_deploy.info.yml&apos;);
  if (isset($info_file[&apos;dependencies&apos;])) {
    \Drupal::service(&apos;module_installer&apos;)-&amp;gt;install($info_file[&apos;dependencies&apos;], TRUE);
  }
}

/**
 * Set the UUID of this website.
 *
 * By default, reinstalling a site will assign it a new random UUID, making
 * it impossible to sync configuration with other instances. This function
 * is called by site deployment module&apos;s .install hook.
 *
 * @param $uuid
 *   A uuid string, for example &apos;e732b460-add4-47a7-8c00-e4dedbb42900&apos;.
 */
function mysite_deploy_set_uuid($uuid) {
  \Drupal::configFactory() -&amp;gt;getEditable(&apos;system.site&apos;)
    -&amp;gt;set(&apos;uuid&apos;, $uuid)
    -&amp;gt;save();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the site deployment module’s .install file looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @file
 * site deployment install functions
 */

/**
 * Implements hook_install().
 */
function mysite_deploy_install() {
  // This module is designed to be enabled on a brand new instance of
  // Drupal. Settings its uuid here will tell this instance that it is
  // in fact the same site as any other instance. Therefore, all local
  // instances, continuous integration, testing, dev, and production
  // instances of a codebase will have the same uuid, enabling us to
  // sync these instances via the config management system.
  // See also https://www.drupal.org/node/2133325
  mysite_deploy_set_uuid(&apos;e732b460-add4-47a7-8c00-e4dedbb42900&apos;);
  for ($i = 8001; $i &amp;lt; 9000; $i++) {
    $candidate = &apos;mysite_deploy_update_&apos; . $i;
    if (function_exists($candidate)) {
      $candidate();
    }
  }
}

/**
 * Update dependencies and revert features
 */
function mysite_deploy_update_8003() {
  // If you add a new dependency during your development:
  // (1) add your dependency to your .info file
  // (2) increment the number in this function name (example: change
  //     change 8003 to 8004)
  // (3) now, on each target environment, running drush updb -y
  //     will call the mysite_deploy_update_dependencies() function
  //     which in turn will enable all new dependencies.
  mysite_deploy_update_dependencies();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The only real difference between a site deployment module for D7 and D8, thus, is that the D8 version must define a UUID common to all instances of a website (local, dev, prod, testing…).&lt;/p&gt;

&lt;h2 id=&quot;configuration-management-directories-active-staging-deploy&quot;&gt;Configuration management directories: active, staging, deploy&lt;/h2&gt;

&lt;p&gt;Out of the box, there are two directories which can contain config management yml files:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The &lt;em&gt;active&lt;/em&gt; directory, which is always empty and unused. It used to be there to store your active configuration, and it is still possible to do so, but &lt;a href=&quot;https://www.drupal.org/node/2323529&quot;&gt;I’m not sure how&lt;/a&gt;. We can ignore this directory for our purposes.&lt;/li&gt;
  &lt;li&gt;The &lt;em&gt;staging&lt;/em&gt; directory, which can contain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.yml&lt;/code&gt; files to be imported into a target site. (For this to work, as mentioned above, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.yml&lt;/code&gt; files will need to have been generated by a site having the same UUID as the target site, or else you will get an error message – on the GUI the error message makes sense, but on the command line you &lt;a href=&quot;https://github.com/drush-ops/drush/issues/807&quot;&gt;will get the cryptic “There were errors validating the config synchronization.”&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will propose a workflow which ignores the staging directory as well, for the following reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, the staging directory is placed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/default/files/&lt;/code&gt;, a directory which contains user data and is explicitly ignored in Drupal’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example.gitignore&lt;/code&gt; file (which makes sense). In our case, we want this information to reside in our git directory.&lt;/li&gt;
  &lt;li&gt;Second, my team has come to rely heavily on reinstalling Drupal and our site deployment module when things get corrupted locally. When you reinstall Drupal using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush si&lt;/code&gt;, the staging directory is deleted, so even if we did have the staging directory in git, we would be prevented from running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush si -y &amp;amp;&amp;amp; drush en mysite_deploy -y&lt;/code&gt;, which we don’t want.&lt;/li&gt;
  &lt;li&gt;Finally, you might want your config directory to be outside of your Drupal root, for security reasons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For all of these reasons, we will add a new “deploy” configuration directory and put it in our git repo, but outside of our Drupal root.&lt;/p&gt;

&lt;p&gt;Our directory hierarchy will now look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysite
  .git
  deploy
    README.txt
    ...
  drupal_root
    CHANGELOG.txt
    core
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also have your deploy directory inside your Drupal root, but keep in mind that certain configuration information are sensitive, containing email addresses and the like. We’ll see later on how to tell Drupal how it can find your “deploy” directory.&lt;/p&gt;

&lt;h2 id=&quot;getting-started-creating-your-drupal-instance&quot;&gt;Getting started: creating your Drupal instance&lt;/h2&gt;

&lt;p&gt;Let’s get started. Make sure you have version 7.x of Drush (compatible with Drupal 8), and create your git repo:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir mysite
cd mysite
mkdir deploy
echo &quot;Contains config meant to be deployed, see http://blog.dcycle.com/blog/68&quot; &amp;gt;&amp;gt; deploy/README.txt
drush dl drupal-8.0.x
mv drupal* drupal_root
cp drupal_root/example.gitignore drupal_root/.gitignore
git init
git add .
git commit -am &apos;initial commit&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let’s install our first instance of the site:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal_root
echo &apos;create database mysite&apos;|mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/mysite -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now create a site deployment module: &lt;a href=&quot;http://blog.dcycle.com/blog/69/drupal-8-site-deployment-module&quot;&gt;here is the code that works for me&lt;/a&gt;. We’ll set the correct site UUID in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysite_deploy.install&lt;/code&gt; later. Add this to git:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add drupal_root/modules/custom
git commit -am &apos;added site deployment module&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let’s tell Drupal where our “deploy” config directory is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Open sites/default/settings.php&lt;/li&gt;
  &lt;li&gt;Find the lines beginning with $config_directories&lt;/li&gt;
  &lt;li&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$config_directories[&apos;deploy&apos;] = &apos;../deploy&apos;;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edit: using a config directory name other than ‘sync’ will &lt;a href=&quot;https://www.drupal.org/node/2916091&quot;&gt;cause an issue Config Split&lt;/a&gt; at the time of this writing.&lt;/p&gt;

&lt;p&gt;We can now perform our first export of our site configuration:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal_root
drush config-export deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You will now notice that your “deploy” directory is filled with your site’s configuration files, and you can add them to git.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add .
git commit -am &apos;added config files&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we need to sync the site UUID from the database to the code, to make sure all subsequent instances of this site have the same UUID. Open deploy/system.site.yml and find UUID property, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uuid: 03821007-701a-4231-8107-7abac53907b1
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now add this same value to your site deployment module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
function mysite_deploy_install() {
  mysite_deploy_set_uuid(&apos;03821007-701a-4231-8107-7abac53907b1&apos;);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;lets-create-a-view-a-content-type-position-a-block&quot;&gt;Let’s create a view! A content type! Position a block!&lt;/h2&gt;

&lt;p&gt;To see how to export configuration, create some views and content types, position some blocks, and change the default theme.&lt;/p&gt;

&lt;p&gt;Now let’s export our changes&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal_root
drush config-export deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your git repo will be changed accordingly&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ..
git status
git add .
git commit -am &apos;changed theme, blocks, content types, views&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;deploying-your-drupal-8-site&quot;&gt;Deploying your Drupal 8 site&lt;/h2&gt;

&lt;p&gt;At this point you can push your code to a git server, and clone it to a dev server. For testing purposes, we will simply clone it directly&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ../
git clone mysite mysite_destination
cd mysite_destination/drupal_root
echo &apos;create database mysite_destination&apos;|mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/mysite_destination -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you visit mysite_destination/drupal_root with a browser, you will see a plain new Drupal 8 site.&lt;/p&gt;

&lt;p&gt;Before continuing, we need to open sites/default/settings.php on mysite_destination and add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$config_directories[&apos;deploy&apos;] = &apos;../deploy&apos;;&lt;/code&gt;, as we did on the source site.&lt;/p&gt;

&lt;p&gt;Now let the magic happen. Let’s enable our site deployment module (to make sure our instance UUID is synched with our source site), and import our configuration from our “deploy” directory:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en mysite_deploy -y
drush config-import deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, on your destination site, you will see all your views, content types, block placements, and the default theme.&lt;/p&gt;

&lt;p&gt;This deployment technique, which can be combined with generated dummy content, allows one to create new instances very quickly for new developers, testing, demos, continuous integration, and for production.&lt;/p&gt;

&lt;h2 id=&quot;incrementally-deploying-your-drupal-8-site&quot;&gt;&lt;em&gt;Incrementally&lt;/em&gt; deploying your Drupal 8 site&lt;/h2&gt;

&lt;p&gt;What about changes you make to the codebase once everything is already deployed. Let’s change a view and run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal_root
drush config-export deploy -y
cd ..
git commit -am &apos;more fields in view&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s deploy this now:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ../mysite_destination
git pull origin master
cd drupal_root
drush config-import deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, incremental deployments are as easy and standardized as initial deployments, reducing the risk of errors, and allowing incremental deployments to be run automatically by a continuous integration server.&lt;/p&gt;

&lt;h2 id=&quot;next-steps-and-conclusion&quot;&gt;Next steps and conclusion&lt;/h2&gt;

&lt;p&gt;Some aspects of your site’s configuration (what makes your site unique) still can’t be exported via the config management system, for example enabling new modules; for that we’ll use &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_update_N/7&quot;&gt;update hooks&lt;/a&gt; as in Drupal 7. As of this writing Drupal 8 update hooks can’t be run with Drush on the command line due to &lt;a href=&quot;https://github.com/drush-ops/drush/issues/47&quot;&gt;this issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, although a great GUI exists for importing and exporting configuration, I chose to do it on the command line so that I could easily create a Jenkins continuous integration job to deploy code to dev and run tests on each push.&lt;/p&gt;

&lt;p&gt;For Drupal projects developed with a dev-stage-prod continuous integration workflow, the new config management system is a great productivity boost.&lt;/p&gt;
</description>
        
          <description>&lt;h2 id=&quot;what-is-code-driven-development-and-why-is-it-done&quot;&gt;What is code-driven development and why is it done?&lt;/h2&gt;

&lt;p&gt;Code-driven development is the practice of placing all development in code. How can development not be in code?, you ask.&lt;/p&gt;

&lt;p&gt;In Drupal, what makes your site unique is often configuration which resides in the database: the current theme, active modules, module-specific configuration, content types, and so on.&lt;/p&gt;

&lt;p&gt;For the purpose of this article, our goal will be for all &lt;em&gt;configuration&lt;/em&gt; (the current theme, the content types, module-specific config, the active module list…) to be in &lt;em&gt;code&lt;/em&gt;, and only &lt;em&gt;content&lt;/em&gt; to be in the database. There are several advantages to this approach:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Because all our configuration is in code, we can package all of it into a single module, which we’ll call a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt;. When enabled, this module should provide a fully workable site without any content.&lt;/li&gt;
  &lt;li&gt;When a site deployment module is combined with generated content, it becomes possible to create new instances of a website &lt;a href=&quot;http://blog.dcycle.com/blog/48/do-not-clone-database&quot;&gt;without cloning the database&lt;/a&gt;. &lt;a href=&quot;https://www.drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devel_generate&lt;/code&gt; module, and &lt;a href=&quot;https://www.drupal.org/project/realistic_dummy_content&quot;&gt;Realistic Dummy Content&lt;/a&gt; can be used to create realistic dummy content. This makes on-ramping new developers easy and consistent.&lt;/li&gt;
  &lt;li&gt;Because unversioned databases are not required to be cloned to set up new environments, your continuous integration server can set up new instances of your site based on a known good starting point, making tests more robust.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;code-driven-development-for-drupal-7&quot;&gt;Code-driven development for Drupal 7&lt;/h2&gt;

&lt;p&gt;Before moving on to D8, let’s look at a typical D7 workflow: The technique I use for developing in Drupal 7 is making sure I have one or more &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;features&lt;/a&gt; with my content types, views, &lt;a href=&quot;http://drupal.org/project/context&quot;&gt;contexts&lt;/a&gt;, and so on; as well as a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt; which contains, in its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file, &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_update_N/7&quot;&gt;update hooks&lt;/a&gt; which revert my features when needed, enable new modules, and programmatically set configuration which can’t be exported via features. That way,&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;incrementally deploying sites is as simple as calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; (to run new update hooks).&lt;/li&gt;
  &lt;li&gt;deploying a site for the first time (or redeploying it from scratch) requires creating the database, enabling our site deployment module (which &lt;a href=&quot;http://blog.dcycle.com/blog/43/run-all-update-hooks-install-hook&quot;&gt;runs all or update hooks&lt;/a&gt;), and optionally generating dummy content if required. For example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush si -y &amp;amp;&amp;amp; drush en mysite_deploy -y &amp;amp;&amp;amp; drush en devel_generate &amp;amp;&amp;amp; drush generate-content 50&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have been using this technique for a few years on all my D7 projects and, in this article, I will explore how something similar can be done in D8.&lt;/p&gt;

&lt;h2 id=&quot;new-in-drupal-8-configuration-management&quot;&gt;New in Drupal 8: configuration management&lt;/h2&gt;

&lt;p&gt;If, like me, you are using &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;features&lt;/a&gt; exclusively to deploy websites (as opposed to using it to bundle generic functionality, for example having a “blog” feature, or a “calendar” feature you can add to any site), config management will replace features in D8. In D7, &lt;a href=&quot;https://www.drupal.org/project/context&quot;&gt;context&lt;/a&gt; is used to provide the ability to export block placement to features, and &lt;a href=&quot;https://www.drupal.org/project/strongarm&quot;&gt;strongarm&lt;/a&gt; exports variables. In D8, variables no longer exist, and block placement is now exportable. All of these modules are thus no longer needed.&lt;/p&gt;

&lt;p&gt;They are replaced by the concept of &lt;a href=&quot;https://www.drupal.org/documentation/administer/config&quot;&gt;configuration management&lt;/a&gt;, a central API for importing and exporting configuration as yml files.&lt;/p&gt;

&lt;h2 id=&quot;configuration-management-and-site-uuids&quot;&gt;Configuration management and site UUIDs&lt;/h2&gt;

&lt;p&gt;In Drupal 8, &lt;a href=&quot;https://www.drupal.org/node/2133325&quot;&gt;sites are now assigned a UUID on install&lt;/a&gt; and configuration can only be synchronized between sites having the same UUID. This is fine if the site has been cloned at some point from one environment to another, but as mentioned above, we are avoiding database cloning: we want it to be possible to install a brand new instance of a site at any time.&lt;/p&gt;

&lt;p&gt;We thus need a mechanism to assign the same UUID to all instances of our site, but still allow us to reinstall it without cloning the database.&lt;/p&gt;

&lt;p&gt;The solution I am using is to assign a site UUID in the site deployment module. Thus, in Drupal 8, my site deployment module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.module&lt;/code&gt; file looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @file
 * site deployment functions
 */
use Drupal\Core\Extension\InfoParser;

/**
 * Updates dependencies based on the site deployment&apos;s info file.
 *
 * If during the course of development, you add a dependency to your
 * site deployment module&apos;s .info file, increment the update hook
 * (see the .install module) and this function will be called, making
 * sure dependencies are enabled.
 */
function mysite_deploy_update_dependencies() {
  $parser = new InfoParser;
  $info_file = $parser-&amp;gt;parse(drupal_get_path(&apos;module&apos;, &apos;mysite_deploy&apos;) . &apos;/mysite_deploy.info.yml&apos;);
  if (isset($info_file[&apos;dependencies&apos;])) {
    \Drupal::service(&apos;module_installer&apos;)-&amp;gt;install($info_file[&apos;dependencies&apos;], TRUE);
  }
}

/**
 * Set the UUID of this website.
 *
 * By default, reinstalling a site will assign it a new random UUID, making
 * it impossible to sync configuration with other instances. This function
 * is called by site deployment module&apos;s .install hook.
 *
 * @param $uuid
 *   A uuid string, for example &apos;e732b460-add4-47a7-8c00-e4dedbb42900&apos;.
 */
function mysite_deploy_set_uuid($uuid) {
  \Drupal::configFactory() -&amp;gt;getEditable(&apos;system.site&apos;)
    -&amp;gt;set(&apos;uuid&apos;, $uuid)
    -&amp;gt;save();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the site deployment module’s .install file looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @file
 * site deployment install functions
 */

/**
 * Implements hook_install().
 */
function mysite_deploy_install() {
  // This module is designed to be enabled on a brand new instance of
  // Drupal. Settings its uuid here will tell this instance that it is
  // in fact the same site as any other instance. Therefore, all local
  // instances, continuous integration, testing, dev, and production
  // instances of a codebase will have the same uuid, enabling us to
  // sync these instances via the config management system.
  // See also https://www.drupal.org/node/2133325
  mysite_deploy_set_uuid(&apos;e732b460-add4-47a7-8c00-e4dedbb42900&apos;);
  for ($i = 8001; $i &amp;lt; 9000; $i++) {
    $candidate = &apos;mysite_deploy_update_&apos; . $i;
    if (function_exists($candidate)) {
      $candidate();
    }
  }
}

/**
 * Update dependencies and revert features
 */
function mysite_deploy_update_8003() {
  // If you add a new dependency during your development:
  // (1) add your dependency to your .info file
  // (2) increment the number in this function name (example: change
  //     change 8003 to 8004)
  // (3) now, on each target environment, running drush updb -y
  //     will call the mysite_deploy_update_dependencies() function
  //     which in turn will enable all new dependencies.
  mysite_deploy_update_dependencies();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The only real difference between a site deployment module for D7 and D8, thus, is that the D8 version must define a UUID common to all instances of a website (local, dev, prod, testing…).&lt;/p&gt;

&lt;h2 id=&quot;configuration-management-directories-active-staging-deploy&quot;&gt;Configuration management directories: active, staging, deploy&lt;/h2&gt;

&lt;p&gt;Out of the box, there are two directories which can contain config management yml files:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The &lt;em&gt;active&lt;/em&gt; directory, which is always empty and unused. It used to be there to store your active configuration, and it is still possible to do so, but &lt;a href=&quot;https://www.drupal.org/node/2323529&quot;&gt;I’m not sure how&lt;/a&gt;. We can ignore this directory for our purposes.&lt;/li&gt;
  &lt;li&gt;The &lt;em&gt;staging&lt;/em&gt; directory, which can contain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.yml&lt;/code&gt; files to be imported into a target site. (For this to work, as mentioned above, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.yml&lt;/code&gt; files will need to have been generated by a site having the same UUID as the target site, or else you will get an error message – on the GUI the error message makes sense, but on the command line you &lt;a href=&quot;https://github.com/drush-ops/drush/issues/807&quot;&gt;will get the cryptic “There were errors validating the config synchronization.”&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will propose a workflow which ignores the staging directory as well, for the following reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, the staging directory is placed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/default/files/&lt;/code&gt;, a directory which contains user data and is explicitly ignored in Drupal’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example.gitignore&lt;/code&gt; file (which makes sense). In our case, we want this information to reside in our git directory.&lt;/li&gt;
  &lt;li&gt;Second, my team has come to rely heavily on reinstalling Drupal and our site deployment module when things get corrupted locally. When you reinstall Drupal using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush si&lt;/code&gt;, the staging directory is deleted, so even if we did have the staging directory in git, we would be prevented from running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush si -y &amp;amp;&amp;amp; drush en mysite_deploy -y&lt;/code&gt;, which we don’t want.&lt;/li&gt;
  &lt;li&gt;Finally, you might want your config directory to be outside of your Drupal root, for security reasons.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For all of these reasons, we will add a new “deploy” configuration directory and put it in our git repo, but outside of our Drupal root.&lt;/p&gt;

&lt;p&gt;Our directory hierarchy will now look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysite
  .git
  deploy
    README.txt
    ...
  drupal_root
    CHANGELOG.txt
    core
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also have your deploy directory inside your Drupal root, but keep in mind that certain configuration information are sensitive, containing email addresses and the like. We’ll see later on how to tell Drupal how it can find your “deploy” directory.&lt;/p&gt;

&lt;h2 id=&quot;getting-started-creating-your-drupal-instance&quot;&gt;Getting started: creating your Drupal instance&lt;/h2&gt;

&lt;p&gt;Let’s get started. Make sure you have version 7.x of Drush (compatible with Drupal 8), and create your git repo:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir mysite
cd mysite
mkdir deploy
echo &quot;Contains config meant to be deployed, see http://blog.dcycle.com/blog/68&quot; &amp;gt;&amp;gt; deploy/README.txt
drush dl drupal-8.0.x
mv drupal* drupal_root
cp drupal_root/example.gitignore drupal_root/.gitignore
git init
git add .
git commit -am &apos;initial commit&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let’s install our first instance of the site:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal_root
echo &apos;create database mysite&apos;|mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/mysite -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now create a site deployment module: &lt;a href=&quot;http://blog.dcycle.com/blog/69/drupal-8-site-deployment-module&quot;&gt;here is the code that works for me&lt;/a&gt;. We’ll set the correct site UUID in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysite_deploy.install&lt;/code&gt; later. Add this to git:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add drupal_root/modules/custom
git commit -am &apos;added site deployment module&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let’s tell Drupal where our “deploy” config directory is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Open sites/default/settings.php&lt;/li&gt;
  &lt;li&gt;Find the lines beginning with $config_directories&lt;/li&gt;
  &lt;li&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$config_directories[&apos;deploy&apos;] = &apos;../deploy&apos;;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edit: using a config directory name other than ‘sync’ will &lt;a href=&quot;https://www.drupal.org/node/2916091&quot;&gt;cause an issue Config Split&lt;/a&gt; at the time of this writing.&lt;/p&gt;

&lt;p&gt;We can now perform our first export of our site configuration:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal_root
drush config-export deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You will now notice that your “deploy” directory is filled with your site’s configuration files, and you can add them to git.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add .
git commit -am &apos;added config files&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we need to sync the site UUID from the database to the code, to make sure all subsequent instances of this site have the same UUID. Open deploy/system.site.yml and find UUID property, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;uuid: 03821007-701a-4231-8107-7abac53907b1
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now add this same value to your site deployment module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
function mysite_deploy_install() {
  mysite_deploy_set_uuid(&apos;03821007-701a-4231-8107-7abac53907b1&apos;);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;lets-create-a-view-a-content-type-position-a-block&quot;&gt;Let’s create a view! A content type! Position a block!&lt;/h2&gt;

&lt;p&gt;To see how to export configuration, create some views and content types, position some blocks, and change the default theme.&lt;/p&gt;

&lt;p&gt;Now let’s export our changes&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal_root
drush config-export deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your git repo will be changed accordingly&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ..
git status
git add .
git commit -am &apos;changed theme, blocks, content types, views&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;deploying-your-drupal-8-site&quot;&gt;Deploying your Drupal 8 site&lt;/h2&gt;

&lt;p&gt;At this point you can push your code to a git server, and clone it to a dev server. For testing purposes, we will simply clone it directly&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ../
git clone mysite mysite_destination
cd mysite_destination/drupal_root
echo &apos;create database mysite_destination&apos;|mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/mysite_destination -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you visit mysite_destination/drupal_root with a browser, you will see a plain new Drupal 8 site.&lt;/p&gt;

&lt;p&gt;Before continuing, we need to open sites/default/settings.php on mysite_destination and add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$config_directories[&apos;deploy&apos;] = &apos;../deploy&apos;;&lt;/code&gt;, as we did on the source site.&lt;/p&gt;

&lt;p&gt;Now let the magic happen. Let’s enable our site deployment module (to make sure our instance UUID is synched with our source site), and import our configuration from our “deploy” directory:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en mysite_deploy -y
drush config-import deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, on your destination site, you will see all your views, content types, block placements, and the default theme.&lt;/p&gt;

&lt;p&gt;This deployment technique, which can be combined with generated dummy content, allows one to create new instances very quickly for new developers, testing, demos, continuous integration, and for production.&lt;/p&gt;

&lt;h2 id=&quot;incrementally-deploying-your-drupal-8-site&quot;&gt;&lt;em&gt;Incrementally&lt;/em&gt; deploying your Drupal 8 site&lt;/h2&gt;

&lt;p&gt;What about changes you make to the codebase once everything is already deployed. Let’s change a view and run:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd drupal_root
drush config-export deploy -y
cd ..
git commit -am &apos;more fields in view&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s deploy this now:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ../mysite_destination
git pull origin master
cd drupal_root
drush config-import deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, incremental deployments are as easy and standardized as initial deployments, reducing the risk of errors, and allowing incremental deployments to be run automatically by a continuous integration server.&lt;/p&gt;

&lt;h2 id=&quot;next-steps-and-conclusion&quot;&gt;Next steps and conclusion&lt;/h2&gt;

&lt;p&gt;Some aspects of your site’s configuration (what makes your site unique) still can’t be exported via the config management system, for example enabling new modules; for that we’ll use &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_update_N/7&quot;&gt;update hooks&lt;/a&gt; as in Drupal 7. As of this writing Drupal 8 update hooks can’t be run with Drush on the command line due to &lt;a href=&quot;https://github.com/drush-ops/drush/issues/47&quot;&gt;this issue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, although a great GUI exists for importing and exporting configuration, I chose to do it on the command line so that I could easily create a Jenkins continuous integration job to deploy code to dev and run tests on each push.&lt;/p&gt;

&lt;p&gt;For Drupal projects developed with a dev-stage-prod continuous integration workflow, the new config management system is a great productivity boost.&lt;/p&gt;
</description>
        
        <pubDate>Wed, 10 Sep 2014 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/68/approach-code-driven-development-drupal-8/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/68/approach-code-driven-development-drupal-8/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>New Drupal 7 project checklist</title>
        <description>&lt;p&gt;I had this checklist documented internally, but I keep referring back to it so I’ll make it available here in case anyone else needs it. The idea here is to document a minimum (not an ideal) set of modules and tasks which I do for almost all projects.&lt;/p&gt;

&lt;h2 id=&quot;questions-to-ask-of-a-client-at-the-project-launch&quot;&gt;Questions to ask of a client at the project launch&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Is your site bilingual? If so is there more than one domain? (if so, and you are exporting your languages as Features, your domain is exported with it. If your domains are different on different environments, you might want to use &lt;a href=&quot;https://www.drupal.org/project/language_domains&quot;&gt;language_domain&lt;/a&gt; to override the domains per environment)&lt;/li&gt;
  &lt;li&gt;What type of compatibility do you need: tablet, mobile, which versions of IE?&lt;/li&gt;
  &lt;li&gt;How do you see your post-launch support and core/module update contract?&lt;/li&gt;
  &lt;li&gt;Do you need SSL support?&lt;/li&gt;
  &lt;li&gt;What is your hosting arrangement?&lt;/li&gt;
  &lt;li&gt;Do you have a contact form?&lt;/li&gt;
  &lt;li&gt;What is your anti-spam method? Note that &lt;a href=&quot;http://www.popsci.com/article/technology/rip-captcha?src=SOC&amp;amp;dom=fb&quot;&gt;CAPTCHA is no longer useful&lt;/a&gt;; I like &lt;a href=&quot;https://mollom.com&quot;&gt;Mollom&lt;/a&gt;, but it’s giving me more and more false positives with time. &lt;a href=&quot;https://www.drupal.org/project/honeypot&quot;&gt;Honeypot&lt;/a&gt; has given me good results as well.&lt;/li&gt;
  &lt;li&gt;Is WYSIWYG required? I strongly suggest &lt;a href=&quot;http://readwrite.com/2012/04/17/why-you-need-to-learn-markdown&quot;&gt;using Markdown instead&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Confirm that all emails are sent in plain text, not HTML. If you’re sending out HTML mail, &lt;a href=&quot;http://www.aweber.com/blog/email-marketing/plain-text-vs-html-email-2014.htm&quot;&gt;do it right&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Do you need an on-site search utility? If so, some thought, and resources, need to go into it or it will be frustrating.&lt;/li&gt;
  &lt;li&gt;What kind of load do you expect on your site (anonymous and admin users)? This information can be used for load testing.&lt;/li&gt;
  &lt;li&gt;If you already have a site, should old paths of critical content map to paths on the new site?&lt;/li&gt;
  &lt;li&gt;Should users be allowed to create accounts (with spam considerations, and see if an admin should approve them).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;sprint-zero-starting-the-project&quot;&gt;&lt;a href=&quot;http://www.scrumalliance.org/community/articles/2013/september/what-is-sprint-zero&quot;&gt;Sprint Zero&lt;/a&gt;: starting the project&lt;/h2&gt;

&lt;p&gt;Here is what should get done in the first Agile sprint, aka Sprint Zero:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If you are using &lt;a href=&quot;http://blog.dcycle.com/blog/46/continuous-deployment-drupal-style&quot;&gt;continuous integration&lt;/a&gt;, a Jenkins job for tracking the master branch: this job should fail if any test fails on the codebase, or if quality metrics (&lt;a href=&quot;https://www.drupal.org/project/coder&quot;&gt;code review&lt;/a&gt;, for example, or &lt;a href=&quot;http://pdepend.org&quot;&gt;pdepend&lt;/a&gt; metrics) reach predefined thresholds.&lt;/li&gt;
  &lt;li&gt;A Jenkins job for pushing to dev. This is triggered by the first job if tests pass. It pushed the new code to the dev environment, and updates the dev environment’s database. &lt;a href=&quot;http://blog.dcycle.com/blog/48/do-not-clone-database&quot;&gt;The database is never cloned&lt;/a&gt;; rather, a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt; is used.&lt;/li&gt;
  &lt;li&gt;An issue queue is set up and the client is given access to it, and training on how to use it.&lt;/li&gt;
  &lt;li&gt;A wiki is set up.&lt;/li&gt;
  &lt;li&gt;A dev environment is set up. This is where the code gets pushed automatically if all tests pass.&lt;/li&gt;
  &lt;li&gt;A prod environment is set up. This environment is normally updated manually after each end of sprint demo.&lt;/li&gt;
  &lt;li&gt;A git repo is set up with a basic Drupal site.&lt;/li&gt;
  &lt;li&gt;A custom module is set up in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/custom&lt;/code&gt;: this is where custom function go.&lt;/li&gt;
  &lt;li&gt;A &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/all/modules/custom&lt;/code&gt;. All deployment-related code and dependencies go here. A &lt;a href=&quot;http://blog.dcycle.com/blog/30/basic-test&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.test&lt;/code&gt;&lt;/a&gt; file and an &lt;a href=&quot;http://blog.dcycle.com/blog/65/basic-install-file-deployment-module&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt;&lt;/a&gt; should be included.&lt;/li&gt;
  &lt;li&gt;A site development module is set up in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/custom&lt;/code&gt;, which is meant to contain all modules required or useful for development, as dependencies.&lt;/li&gt;
  &lt;li&gt;A custom theme is created.&lt;/li&gt;
  &lt;li&gt;An initial feature is created in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/features&lt;/code&gt;. This is where all your features will be added.&lt;/li&gt;
  &lt;li&gt;A “sites/*/modules/patches” folder is created (with a README.txt file, to make sure it goes into git). This is where core and contrib patches should go. Your site’s maintainers should apply these patches when core or contrib modules are updated. Patch names here should include the node id and comment number on Drupal.org.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;basic-module-list-always-used&quot;&gt;Basic module list (always used)&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/views&quot;&gt;views&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/context&quot;&gt;context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/strongarm&quot;&gt;strongarm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;admin_menu_toolbar (part of &lt;a href=&quot;https://drupal.org/project/admin_menu&quot;&gt;admin_menu&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/markdown&quot;&gt;markdown&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/transliteration&quot;&gt;transliteration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/globalredirect&quot;&gt;globalredirect&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/redirect&quot;&gt;redirect&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/config_builder&quot;&gt;config_builder&lt;/a&gt;, almost always useful to create a single form allowing clients to change site variables.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/features&quot;&gt;features&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/logintoboggan&quot;&gt;logintoboggan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;development-modules-not-enabled-on-production&quot;&gt;Development modules (not enabled on production)&lt;/h2&gt;

&lt;p&gt;I normally create a custom development module with these as dependencies:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/diff&quot;&gt;diff&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;devel&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;realistic_dummy_content_api (part of &lt;a href=&quot;https://drupal.org/project/realistic_dummy_content&quot;&gt;realistic_dummy_content&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;coder_review (part of &lt;a href=&quot;https://drupal.org/project/coder&quot;&gt;coder&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;context_ui (part of &lt;a href=&quot;https://drupal.org/project/context&quot;&gt;context&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;views_ui (part of &lt;a href=&quot;https://drupal.org/project/views&quot;&gt;views&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/masquerade&quot;&gt;masquerade&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/search_krumo&quot;&gt;search_krumo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/simpletest_turbo&quot;&gt;simpletest_turbo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;devel_generate (part of &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;devel&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;config_builder_ui (part of &lt;a href=&quot;https://drupal.org/project/config_builder&quot;&gt;config_builder&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/maillog&quot;&gt;maillog&lt;/a&gt;, to keep track of sent email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I make sure this module is in my repo but it is not enabled unless used:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/devel_themer&quot;&gt;devel_themer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;experimental-modules&quot;&gt;Experimental modules&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/dcycle&quot;&gt;dcycle&lt;/a&gt;, this is a module that is in active development, not ready for prime yet, but where I try to add all my code to help with testing, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;multilingual-modules&quot;&gt;Multilingual modules&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/i18n&quot;&gt;i18n&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/potx&quot;&gt;potx&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/l10n_update&quot;&gt;l10n_update&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/entity_translation&quot;&gt;entity_translation&lt;/a&gt; if you need the same node id to display in several languages. This is useful if you have references to nodes which should be translated.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/title&quot;&gt;title&lt;/a&gt; if you are using entity translations and your titles can be multilingual.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;launch-checklist&quot;&gt;Launch checklist&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Design a custom 404, error and maintenance page.&lt;/li&gt;
  &lt;li&gt;Path, alias and permalink strategy. (Might require &lt;a href=&quot;https://drupal.org/project/pathauto&quot;&gt;pathauto&lt;/a&gt;.)&lt;/li&gt;
  &lt;li&gt;Think of adding revisions to content types to avoid clients losing their data.&lt;/li&gt;
  &lt;li&gt;Don’t display errors on production.&lt;/li&gt;
  &lt;li&gt;Optimize CSS, JS and page caching.&lt;/li&gt;
  &lt;li&gt;Views should be cached.&lt;/li&gt;
  &lt;li&gt;System messages are properly themed.&lt;/li&gt;
  &lt;li&gt;Prevent very simple passwords.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://linuxdev.dk/blog/sending-drupal-log-enteries-syslog&quot;&gt;Using syslog instead of dblog&lt;/a&gt; on prod&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h2&gt;

&lt;p&gt;Most shops, and most developers, have some sort of checklist like this. Mine is not any better or worse than most, but can be a good starting point. Another note: I’ve seen at least three Drupal teams try, and fail, to implement a “Drupal Starter kit for Company XYZ” and keep it under version control. The problem with that approach, as opposed to a checklist, is that it’s not lightweight enough: it is a software product which needs maintenance, and after a while no one maintains it.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;I had this checklist documented internally, but I keep referring back to it so I’ll make it available here in case anyone else needs it. The idea here is to document a minimum (not an ideal) set of modules and tasks which I do for almost all projects.&lt;/p&gt;

&lt;h2 id=&quot;questions-to-ask-of-a-client-at-the-project-launch&quot;&gt;Questions to ask of a client at the project launch&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Is your site bilingual? If so is there more than one domain? (if so, and you are exporting your languages as Features, your domain is exported with it. If your domains are different on different environments, you might want to use &lt;a href=&quot;https://www.drupal.org/project/language_domains&quot;&gt;language_domain&lt;/a&gt; to override the domains per environment)&lt;/li&gt;
  &lt;li&gt;What type of compatibility do you need: tablet, mobile, which versions of IE?&lt;/li&gt;
  &lt;li&gt;How do you see your post-launch support and core/module update contract?&lt;/li&gt;
  &lt;li&gt;Do you need SSL support?&lt;/li&gt;
  &lt;li&gt;What is your hosting arrangement?&lt;/li&gt;
  &lt;li&gt;Do you have a contact form?&lt;/li&gt;
  &lt;li&gt;What is your anti-spam method? Note that &lt;a href=&quot;http://www.popsci.com/article/technology/rip-captcha?src=SOC&amp;amp;dom=fb&quot;&gt;CAPTCHA is no longer useful&lt;/a&gt;; I like &lt;a href=&quot;https://mollom.com&quot;&gt;Mollom&lt;/a&gt;, but it’s giving me more and more false positives with time. &lt;a href=&quot;https://www.drupal.org/project/honeypot&quot;&gt;Honeypot&lt;/a&gt; has given me good results as well.&lt;/li&gt;
  &lt;li&gt;Is WYSIWYG required? I strongly suggest &lt;a href=&quot;http://readwrite.com/2012/04/17/why-you-need-to-learn-markdown&quot;&gt;using Markdown instead&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Confirm that all emails are sent in plain text, not HTML. If you’re sending out HTML mail, &lt;a href=&quot;http://www.aweber.com/blog/email-marketing/plain-text-vs-html-email-2014.htm&quot;&gt;do it right&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Do you need an on-site search utility? If so, some thought, and resources, need to go into it or it will be frustrating.&lt;/li&gt;
  &lt;li&gt;What kind of load do you expect on your site (anonymous and admin users)? This information can be used for load testing.&lt;/li&gt;
  &lt;li&gt;If you already have a site, should old paths of critical content map to paths on the new site?&lt;/li&gt;
  &lt;li&gt;Should users be allowed to create accounts (with spam considerations, and see if an admin should approve them).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;sprint-zero-starting-the-project&quot;&gt;&lt;a href=&quot;http://www.scrumalliance.org/community/articles/2013/september/what-is-sprint-zero&quot;&gt;Sprint Zero&lt;/a&gt;: starting the project&lt;/h2&gt;

&lt;p&gt;Here is what should get done in the first Agile sprint, aka Sprint Zero:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If you are using &lt;a href=&quot;http://blog.dcycle.com/blog/46/continuous-deployment-drupal-style&quot;&gt;continuous integration&lt;/a&gt;, a Jenkins job for tracking the master branch: this job should fail if any test fails on the codebase, or if quality metrics (&lt;a href=&quot;https://www.drupal.org/project/coder&quot;&gt;code review&lt;/a&gt;, for example, or &lt;a href=&quot;http://pdepend.org&quot;&gt;pdepend&lt;/a&gt; metrics) reach predefined thresholds.&lt;/li&gt;
  &lt;li&gt;A Jenkins job for pushing to dev. This is triggered by the first job if tests pass. It pushed the new code to the dev environment, and updates the dev environment’s database. &lt;a href=&quot;http://blog.dcycle.com/blog/48/do-not-clone-database&quot;&gt;The database is never cloned&lt;/a&gt;; rather, a &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt; is used.&lt;/li&gt;
  &lt;li&gt;An issue queue is set up and the client is given access to it, and training on how to use it.&lt;/li&gt;
  &lt;li&gt;A wiki is set up.&lt;/li&gt;
  &lt;li&gt;A dev environment is set up. This is where the code gets pushed automatically if all tests pass.&lt;/li&gt;
  &lt;li&gt;A prod environment is set up. This environment is normally updated manually after each end of sprint demo.&lt;/li&gt;
  &lt;li&gt;A git repo is set up with a basic Drupal site.&lt;/li&gt;
  &lt;li&gt;A custom module is set up in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/custom&lt;/code&gt;: this is where custom function go.&lt;/li&gt;
  &lt;li&gt;A &lt;a href=&quot;http://blog.dcycle.com/blog/44/what-site-deployment-module&quot;&gt;site deployment module&lt;/a&gt; in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/all/modules/custom&lt;/code&gt;. All deployment-related code and dependencies go here. A &lt;a href=&quot;http://blog.dcycle.com/blog/30/basic-test&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.test&lt;/code&gt;&lt;/a&gt; file and an &lt;a href=&quot;http://blog.dcycle.com/blog/65/basic-install-file-deployment-module&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt;&lt;/a&gt; should be included.&lt;/li&gt;
  &lt;li&gt;A site development module is set up in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/custom&lt;/code&gt;, which is meant to contain all modules required or useful for development, as dependencies.&lt;/li&gt;
  &lt;li&gt;A custom theme is created.&lt;/li&gt;
  &lt;li&gt;An initial feature is created in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/features&lt;/code&gt;. This is where all your features will be added.&lt;/li&gt;
  &lt;li&gt;A “sites/*/modules/patches” folder is created (with a README.txt file, to make sure it goes into git). This is where core and contrib patches should go. Your site’s maintainers should apply these patches when core or contrib modules are updated. Patch names here should include the node id and comment number on Drupal.org.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;basic-module-list-always-used&quot;&gt;Basic module list (always used)&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/views&quot;&gt;views&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/context&quot;&gt;context&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/strongarm&quot;&gt;strongarm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;admin_menu_toolbar (part of &lt;a href=&quot;https://drupal.org/project/admin_menu&quot;&gt;admin_menu&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/markdown&quot;&gt;markdown&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/transliteration&quot;&gt;transliteration&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/globalredirect&quot;&gt;globalredirect&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/redirect&quot;&gt;redirect&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/config_builder&quot;&gt;config_builder&lt;/a&gt;, almost always useful to create a single form allowing clients to change site variables.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/features&quot;&gt;features&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/logintoboggan&quot;&gt;logintoboggan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;development-modules-not-enabled-on-production&quot;&gt;Development modules (not enabled on production)&lt;/h2&gt;

&lt;p&gt;I normally create a custom development module with these as dependencies:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/diff&quot;&gt;diff&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;devel&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;realistic_dummy_content_api (part of &lt;a href=&quot;https://drupal.org/project/realistic_dummy_content&quot;&gt;realistic_dummy_content&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;coder_review (part of &lt;a href=&quot;https://drupal.org/project/coder&quot;&gt;coder&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;context_ui (part of &lt;a href=&quot;https://drupal.org/project/context&quot;&gt;context&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;views_ui (part of &lt;a href=&quot;https://drupal.org/project/views&quot;&gt;views&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/masquerade&quot;&gt;masquerade&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/search_krumo&quot;&gt;search_krumo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/simpletest_turbo&quot;&gt;simpletest_turbo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;devel_generate (part of &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;devel&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;config_builder_ui (part of &lt;a href=&quot;https://drupal.org/project/config_builder&quot;&gt;config_builder&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/maillog&quot;&gt;maillog&lt;/a&gt;, to keep track of sent email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I make sure this module is in my repo but it is not enabled unless used:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/devel_themer&quot;&gt;devel_themer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;experimental-modules&quot;&gt;Experimental modules&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/dcycle&quot;&gt;dcycle&lt;/a&gt;, this is a module that is in active development, not ready for prime yet, but where I try to add all my code to help with testing, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;multilingual-modules&quot;&gt;Multilingual modules&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/i18n&quot;&gt;i18n&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/potx&quot;&gt;potx&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://drupal.org/project/l10n_update&quot;&gt;l10n_update&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/entity_translation&quot;&gt;entity_translation&lt;/a&gt; if you need the same node id to display in several languages. This is useful if you have references to nodes which should be translated.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/title&quot;&gt;title&lt;/a&gt; if you are using entity translations and your titles can be multilingual.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;launch-checklist&quot;&gt;Launch checklist&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Design a custom 404, error and maintenance page.&lt;/li&gt;
  &lt;li&gt;Path, alias and permalink strategy. (Might require &lt;a href=&quot;https://drupal.org/project/pathauto&quot;&gt;pathauto&lt;/a&gt;.)&lt;/li&gt;
  &lt;li&gt;Think of adding revisions to content types to avoid clients losing their data.&lt;/li&gt;
  &lt;li&gt;Don’t display errors on production.&lt;/li&gt;
  &lt;li&gt;Optimize CSS, JS and page caching.&lt;/li&gt;
  &lt;li&gt;Views should be cached.&lt;/li&gt;
  &lt;li&gt;System messages are properly themed.&lt;/li&gt;
  &lt;li&gt;Prevent very simple passwords.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://linuxdev.dk/blog/sending-drupal-log-enteries-syslog&quot;&gt;Using syslog instead of dblog&lt;/a&gt; on prod&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;in-conclusion&quot;&gt;In conclusion&lt;/h2&gt;

&lt;p&gt;Most shops, and most developers, have some sort of checklist like this. Mine is not any better or worse than most, but can be a good starting point. Another note: I’ve seen at least three Drupal teams try, and fail, to implement a “Drupal Starter kit for Company XYZ” and keep it under version control. The problem with that approach, as opposed to a checklist, is that it’s not lightweight enough: it is a software product which needs maintenance, and after a while no one maintains it.&lt;/p&gt;
</description>
        
        <pubDate>Wed, 30 Jul 2014 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/66/new-drupal-7-project-checklist/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/66/new-drupal-7-project-checklist/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Test your sad path first</title>
        <description>&lt;p&gt;One of the techniques I use to make sure I write tests is to write them before I do anything else, which is known as test-driven development. If you develop your functionality before writing a test, in most cases you will never write the test to go with it, because you will be pressured to move on to new features.&lt;/p&gt;

&lt;p&gt;I have found, though, that when writing tests, our team tends to think only about the happy path: what happens if everything goes according to plan.&lt;/p&gt;

&lt;p&gt;Let me give an quick example: let’s say you are developing a donation system for anonymous users to make donations on your site. The user story calls for a form where a donation amount can be entered before redirecting the user to the payment form. Using test-driven development and Drupal’s Simpletest framework, we might start by writing something like this in our &lt;a href=&quot;http://blog.dcycle.com/node/44&quot;&gt;site deployment module&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.test&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// @file mysite_deploy.test

class MysiteDonate extends DrupalWebTestCase {

  ...

  public function testSite() {

    $edit = array(
      &apos;amount&apos; =&amp;gt; 420,
    );
    ...
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    ...
    $this-&amp;gt;assertText(&apos;You are about to donate $420&apos;, &apos;The donation amount has been recorded&apos;);
  }

  ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you first run this test it will fail, and your job as a developer will be to make this test pass. That’s test-driven development.&lt;/p&gt;

&lt;p&gt;The problem with this approach is that it only defines the happy path: what should happen when all goes according to plan. It makes no provision for the sad path: what happens if a user puts something other than a number? What happens if 0 is entered? These are known as sad paths, and most teams never think about them until they occur (human nature, I guess).&lt;/p&gt;

&lt;p&gt;To make sure we think about the sad path, I start by making sure the right questions are asked during our Agile sprint planning sessions. In the case of the “donation” user story mentioned above, the following business questions should be asked during sprint planning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What’s the minimum donation? Obviously it should not be possible to donate $0, but is $0.01 OK?&lt;/li&gt;
  &lt;li&gt;Is there a maximum donation? Should the system bring you to the checkout page if you enter 1 billion dollars in the donation box?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Often, the client will not have thought of that, and will answer something like: sure there should be a minimum and a maximum, and we also want site administrators to be able to edit those. Let’s say the team agrees on this (and the extra work it entails), the admin interface too should be tested.&lt;/p&gt;

&lt;p&gt;Once the sprint planning session is over, I will start by writing the test based on business considerations above, and also integrating other sad paths I can think of, into my test.&lt;/p&gt;

&lt;p&gt;Here is what our test might look like now, assuming we have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setUp()&lt;/code&gt; function which enables our &lt;a href=&quot;http://blog.dcycle.com/node/44&quot;&gt;site deployment module&lt;/a&gt; and dependent features (including roles); and we are using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loginAsRole()&lt;/code&gt; method, &lt;a href=&quot;http://blog.dcycle.com/blog/45&quot;&gt;documented here&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// @file mysite_deploy.test

class MysiteDonate extends DrupalWebTestCase {

  ...

  public function testSite() {

    // Manage minimum and maximum donation amounts.
    $this-&amp;gt;drupalGet(&apos;admin/options&apos;);
    $this-&amp;gt;assertText(&apos;Access denied&apos;, &apos;Non-admin users cannot access the configuration page&apos;);
    $this-&amp;gt;loginAsRole(&apos;administrator&apos;);
    $edit = array(
      &apos;minimum&apos; =&amp;gt; &apos;50&apos;,
      &apos;maximum&apos; =&amp;gt; $this-&amp;gt;randomName(),
    );
    $this-&amp;gt;drupalPost(&apos;admin/option&apos;, $edit, &apos;Save&apos;);
    $this-&amp;gt;assertText(&apos;Minimum and maximum donation amounts must be numeric&apos;);
    $edit[&apos;maximum&apos;] = &apos;40&apos;;
    $this-&amp;gt;drupalPost(&apos;admin/option&apos;, $edit, &apos;Save&apos;);
    $this-&amp;gt;assertText(&apos;Minimum amount must be equal to or less than maximum donation amount&apos;);
    $edit[&apos;maximum&apos;] = &apos;30&apos;;
    $this-&amp;gt;drupalPost(&apos;admin/option&apos;, $edit, &apos;Save&apos;);
    $this-&amp;gt;assertText(&apos;Minimum maximum donation amounts have been saved&apos;);
    $this-&amp;gt;drupalLogout();

    // Make a donation, sad path
    $edit = array(
      &apos;amount&apos; =&amp;gt; &apos;&amp;lt;script&amp;gt;alert(&quot;hello!&quot;)&amp;lt;/script&amp;gt;&apos;,
    );
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    $this-&amp;gt;assertText(&apos;Donation amount must be numeric&apos;, &apos;Intercept non-numeric input.&apos;);
    $edit[&apos;amount&apos;] = 29;
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    $this-&amp;gt;assertText(&apos;Thanks for your generosity, but we do not accept donations below $30.&apos;);
    $edit[&apos;amount&apos;] = 41;
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    $this-&amp;gt;assertText(&apos;Wow, $41! Do not do this through our website, please contact us and we will discuss this over the phone.&apos;);

    // Make a donation, happy path
    $edit[&apos;amount&apos;] = 30;
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    $this-&amp;gt;assertText(&apos;You are about to donate $30&apos;, &apos;The donation amount has been recorded&apos;);
  }

  ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above example is a much more complete portrait of what your site should do, and documenting everything in a failing test even before you or someone else starts coding ensures you don’t forget validations and the like.&lt;/p&gt;

&lt;p&gt;One interesting thing to note about our complete test is that sad paths actually take up &lt;em&gt;a lot&lt;/em&gt; more effort than the happy path. There are many advantages to thinking of them first:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The client can be involved in making business decisions which can affect the sad path.&lt;/li&gt;
  &lt;li&gt;The entire team (including the client) is made aware as early as possible about sad path considerations, and the extra work they entail.&lt;/li&gt;
  &lt;li&gt;Nothing is taken for granted as obvious: time is set aside for sad path development.&lt;/li&gt;
  &lt;li&gt;The sad path becomes an integral part of your user story which can be part of the demo. Often in Agile sprint reviews, if no one has ever thought of the sad path, only the happy path is demonstrated.&lt;/li&gt;
  &lt;li&gt;There is less technical debt associated with sad path development: you are less likely to get a panicked call from your client once your site goes live about getting dozens of 50 cent donations when the payment processor is taking a dollar in fees.&lt;/li&gt;
  &lt;li&gt;Your code will be more secure: you will think about how your system can be hacked and integrate hacking attempts (and the appropriate response) directly into your test.&lt;/li&gt;
  &lt;li&gt;You will be more confident putting a failing test on a feature branch and handing it to junior developers: they will be less likely to forget something.&lt;/li&gt;
  &lt;li&gt;Thinking of the sad path can make you reconsider how to define your features: a contact form or commenting system can seem trivial when you only think of the happy path. However, when you take into account how to deal with spam, you might decide to not allow comments at all, or to allow only authenticated users to post comments or use the contact form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that as in all test-driven development, your test is not set in stone. It is like any other code: developers can modify it as long as they follow the spirit of your test. For example, maybe your config page is not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/option&lt;/code&gt; but something else. Developers should feel that they own the test and can change it to fit the real system.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;One of the techniques I use to make sure I write tests is to write them before I do anything else, which is known as test-driven development. If you develop your functionality before writing a test, in most cases you will never write the test to go with it, because you will be pressured to move on to new features.&lt;/p&gt;

&lt;p&gt;I have found, though, that when writing tests, our team tends to think only about the happy path: what happens if everything goes according to plan.&lt;/p&gt;

&lt;p&gt;Let me give an quick example: let’s say you are developing a donation system for anonymous users to make donations on your site. The user story calls for a form where a donation amount can be entered before redirecting the user to the payment form. Using test-driven development and Drupal’s Simpletest framework, we might start by writing something like this in our &lt;a href=&quot;http://blog.dcycle.com/node/44&quot;&gt;site deployment module&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.test&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// @file mysite_deploy.test

class MysiteDonate extends DrupalWebTestCase {

  ...

  public function testSite() {

    $edit = array(
      &apos;amount&apos; =&amp;gt; 420,
    );
    ...
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    ...
    $this-&amp;gt;assertText(&apos;You are about to donate $420&apos;, &apos;The donation amount has been recorded&apos;);
  }

  ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you first run this test it will fail, and your job as a developer will be to make this test pass. That’s test-driven development.&lt;/p&gt;

&lt;p&gt;The problem with this approach is that it only defines the happy path: what should happen when all goes according to plan. It makes no provision for the sad path: what happens if a user puts something other than a number? What happens if 0 is entered? These are known as sad paths, and most teams never think about them until they occur (human nature, I guess).&lt;/p&gt;

&lt;p&gt;To make sure we think about the sad path, I start by making sure the right questions are asked during our Agile sprint planning sessions. In the case of the “donation” user story mentioned above, the following business questions should be asked during sprint planning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What’s the minimum donation? Obviously it should not be possible to donate $0, but is $0.01 OK?&lt;/li&gt;
  &lt;li&gt;Is there a maximum donation? Should the system bring you to the checkout page if you enter 1 billion dollars in the donation box?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Often, the client will not have thought of that, and will answer something like: sure there should be a minimum and a maximum, and we also want site administrators to be able to edit those. Let’s say the team agrees on this (and the extra work it entails), the admin interface too should be tested.&lt;/p&gt;

&lt;p&gt;Once the sprint planning session is over, I will start by writing the test based on business considerations above, and also integrating other sad paths I can think of, into my test.&lt;/p&gt;

&lt;p&gt;Here is what our test might look like now, assuming we have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setUp()&lt;/code&gt; function which enables our &lt;a href=&quot;http://blog.dcycle.com/node/44&quot;&gt;site deployment module&lt;/a&gt; and dependent features (including roles); and we are using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loginAsRole()&lt;/code&gt; method, &lt;a href=&quot;http://blog.dcycle.com/blog/45&quot;&gt;documented here&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// @file mysite_deploy.test

class MysiteDonate extends DrupalWebTestCase {

  ...

  public function testSite() {

    // Manage minimum and maximum donation amounts.
    $this-&amp;gt;drupalGet(&apos;admin/options&apos;);
    $this-&amp;gt;assertText(&apos;Access denied&apos;, &apos;Non-admin users cannot access the configuration page&apos;);
    $this-&amp;gt;loginAsRole(&apos;administrator&apos;);
    $edit = array(
      &apos;minimum&apos; =&amp;gt; &apos;50&apos;,
      &apos;maximum&apos; =&amp;gt; $this-&amp;gt;randomName(),
    );
    $this-&amp;gt;drupalPost(&apos;admin/option&apos;, $edit, &apos;Save&apos;);
    $this-&amp;gt;assertText(&apos;Minimum and maximum donation amounts must be numeric&apos;);
    $edit[&apos;maximum&apos;] = &apos;40&apos;;
    $this-&amp;gt;drupalPost(&apos;admin/option&apos;, $edit, &apos;Save&apos;);
    $this-&amp;gt;assertText(&apos;Minimum amount must be equal to or less than maximum donation amount&apos;);
    $edit[&apos;maximum&apos;] = &apos;30&apos;;
    $this-&amp;gt;drupalPost(&apos;admin/option&apos;, $edit, &apos;Save&apos;);
    $this-&amp;gt;assertText(&apos;Minimum maximum donation amounts have been saved&apos;);
    $this-&amp;gt;drupalLogout();

    // Make a donation, sad path
    $edit = array(
      &apos;amount&apos; =&amp;gt; &apos;&amp;lt;script&amp;gt;alert(&quot;hello!&quot;)&amp;lt;/script&amp;gt;&apos;,
    );
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    $this-&amp;gt;assertText(&apos;Donation amount must be numeric&apos;, &apos;Intercept non-numeric input.&apos;);
    $edit[&apos;amount&apos;] = 29;
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    $this-&amp;gt;assertText(&apos;Thanks for your generosity, but we do not accept donations below $30.&apos;);
    $edit[&apos;amount&apos;] = 41;
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    $this-&amp;gt;assertText(&apos;Wow, $41! Do not do this through our website, please contact us and we will discuss this over the phone.&apos;);

    // Make a donation, happy path
    $edit[&apos;amount&apos;] = 30;
    $this-&amp;gt;drupalPost(&apos;donate&apos;, $edit, &apos;Donate now!&apos;);
    $this-&amp;gt;assertText(&apos;You are about to donate $30&apos;, &apos;The donation amount has been recorded&apos;);
  }

  ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above example is a much more complete portrait of what your site should do, and documenting everything in a failing test even before you or someone else starts coding ensures you don’t forget validations and the like.&lt;/p&gt;

&lt;p&gt;One interesting thing to note about our complete test is that sad paths actually take up &lt;em&gt;a lot&lt;/em&gt; more effort than the happy path. There are many advantages to thinking of them first:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The client can be involved in making business decisions which can affect the sad path.&lt;/li&gt;
  &lt;li&gt;The entire team (including the client) is made aware as early as possible about sad path considerations, and the extra work they entail.&lt;/li&gt;
  &lt;li&gt;Nothing is taken for granted as obvious: time is set aside for sad path development.&lt;/li&gt;
  &lt;li&gt;The sad path becomes an integral part of your user story which can be part of the demo. Often in Agile sprint reviews, if no one has ever thought of the sad path, only the happy path is demonstrated.&lt;/li&gt;
  &lt;li&gt;There is less technical debt associated with sad path development: you are less likely to get a panicked call from your client once your site goes live about getting dozens of 50 cent donations when the payment processor is taking a dollar in fees.&lt;/li&gt;
  &lt;li&gt;Your code will be more secure: you will think about how your system can be hacked and integrate hacking attempts (and the appropriate response) directly into your test.&lt;/li&gt;
  &lt;li&gt;You will be more confident putting a failing test on a feature branch and handing it to junior developers: they will be less likely to forget something.&lt;/li&gt;
  &lt;li&gt;Thinking of the sad path can make you reconsider how to define your features: a contact form or commenting system can seem trivial when you only think of the happy path. However, when you take into account how to deal with spam, you might decide to not allow comments at all, or to allow only authenticated users to post comments or use the contact form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that as in all test-driven development, your test is not set in stone. It is like any other code: developers can modify it as long as they follow the spirit of your test. For example, maybe your config page is not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/option&lt;/code&gt; but something else. Developers should feel that they own the test and can change it to fit the real system.&lt;/p&gt;
</description>
        
        <pubDate>Fri, 23 May 2014 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/63/test-your-sad-path-first/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/63/test-your-sad-path-first/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Simpletest Turbo: how I almost quadrupled the speed of my tests</title>
        <description>&lt;p&gt;My development team is using a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; which, when enabled, deploys our entire website (with translations, views, content types, the default theme, etc.).&lt;/p&gt;

&lt;p&gt;We defined about 30 tests (and counting) which are linked to Agile user stories and confirm that the site is doing what it’s supposed to do. These tests are defined in Drupal’s own Simpletest framework, and works as follows: for every test, our site deployment module is enabled on a new database (&lt;a href=&quot;http://blog.dcycle.com/blog/48/do-not-clone-database&quot;&gt;the database is never cloned&lt;/a&gt;), which can take about two minutes; the test is run, and then the temporary database is destroyed.&lt;/p&gt;

&lt;p&gt;This created the following problem: because we were deploying our site 30 times during our test run, a single test run was taking over 90 minutes. Furthermore, we are halfway into the project, and we anticipate doubling, perhaps tripling our test coverage, which would mean our tests would take over four hours to run.&lt;/p&gt;

&lt;p&gt;Now, we have a Jenkins server which performs all the tests every time a change is detected in Git, but even so, when several people are pushing to the git repo, test results which are 90 minutes old tend to be harder to debug, and developers tend to ignore, subvert and resent the whole testing process.&lt;/p&gt;

&lt;p&gt;We could combine tests so the site would be deployed less often during the testing process, but this causes another problem: tests which are hundreds of lines long, and which validate unrelated functionality, are harder to debug than short tests, so it is not a satisfactory solution.&lt;/p&gt;

&lt;p&gt;When we look at what is taking so long, we notice that a majority of the processing power goes to install (deploy) our testing environment &lt;em&gt;for each test&lt;/em&gt;, which is then destroyed after a very short test.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href=&quot;https://drupal.org/project/simpletest_turbo&quot;&gt;Simpletest Turbo&lt;/a&gt;, which provides very simple code to &lt;em&gt;cache&lt;/em&gt; your database once the setUp() function is run, so the next test can simply reuse the same database starting point rather than recreate everything from scratch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/screen_shot_2014-04-22_at_3.13.55_pm.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Although Simpletest Turbo is in early stages of development, I have used it to almost &lt;em&gt;quadruple the speed of my tests&lt;/em&gt;, as you can see from this Jenkins trend chart:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/screen_shot_2014-04-22_at_3.14.08_pm.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I know: my tests are failing more than I would like them to, but now I’m getting feedback every 25 minutes instead of every 95 minutes, so failures are easier to pinpoint and fix.&lt;/p&gt;

&lt;p&gt;Furthermore, fairly little time is spent deploying the site: this is done once, and the following tests use a cached deployment, so we are not merely speeding up our tests (as we would if we were adding hardware): we are streamlining duplicate effort. It thus becomes relatively cheap to add new independent tests, because they are using a cached site setup.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;My development team is using a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; which, when enabled, deploys our entire website (with translations, views, content types, the default theme, etc.).&lt;/p&gt;

&lt;p&gt;We defined about 30 tests (and counting) which are linked to Agile user stories and confirm that the site is doing what it’s supposed to do. These tests are defined in Drupal’s own Simpletest framework, and works as follows: for every test, our site deployment module is enabled on a new database (&lt;a href=&quot;http://blog.dcycle.com/blog/48/do-not-clone-database&quot;&gt;the database is never cloned&lt;/a&gt;), which can take about two minutes; the test is run, and then the temporary database is destroyed.&lt;/p&gt;

&lt;p&gt;This created the following problem: because we were deploying our site 30 times during our test run, a single test run was taking over 90 minutes. Furthermore, we are halfway into the project, and we anticipate doubling, perhaps tripling our test coverage, which would mean our tests would take over four hours to run.&lt;/p&gt;

&lt;p&gt;Now, we have a Jenkins server which performs all the tests every time a change is detected in Git, but even so, when several people are pushing to the git repo, test results which are 90 minutes old tend to be harder to debug, and developers tend to ignore, subvert and resent the whole testing process.&lt;/p&gt;

&lt;p&gt;We could combine tests so the site would be deployed less often during the testing process, but this causes another problem: tests which are hundreds of lines long, and which validate unrelated functionality, are harder to debug than short tests, so it is not a satisfactory solution.&lt;/p&gt;

&lt;p&gt;When we look at what is taking so long, we notice that a majority of the processing power goes to install (deploy) our testing environment &lt;em&gt;for each test&lt;/em&gt;, which is then destroyed after a very short test.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href=&quot;https://drupal.org/project/simpletest_turbo&quot;&gt;Simpletest Turbo&lt;/a&gt;, which provides very simple code to &lt;em&gt;cache&lt;/em&gt; your database once the setUp() function is run, so the next test can simply reuse the same database starting point rather than recreate everything from scratch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/screen_shot_2014-04-22_at_3.13.55_pm.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Although Simpletest Turbo is in early stages of development, I have used it to almost &lt;em&gt;quadruple the speed of my tests&lt;/em&gt;, as you can see from this Jenkins trend chart:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/screen_shot_2014-04-22_at_3.14.08_pm.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I know: my tests are failing more than I would like them to, but now I’m getting feedback every 25 minutes instead of every 95 minutes, so failures are easier to pinpoint and fix.&lt;/p&gt;

&lt;p&gt;Furthermore, fairly little time is spent deploying the site: this is done once, and the following tests use a cached deployment, so we are not merely speeding up our tests (as we would if we were adding hardware): we are streamlining duplicate effort. It thus becomes relatively cheap to add new independent tests, because they are using a cached site setup.&lt;/p&gt;
</description>
        
        <pubDate>Tue, 22 Apr 2014 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/58/simpletest-turbo-how-i-almost-quadrupled-speed-my-tests/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/58/simpletest-turbo-how-i-almost-quadrupled-speed-my-tests/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Eight tips to remember on your path to automated testing</title>
        <description>&lt;p&gt;Many Drupal projects now under maintenance suffer from technical debt: a lot of the functionality is in the database and outside of git, and the code lacks automated testing. Furthermore, the functionality is often brittle: a change to one feature breaks something seemingly unrelated.&lt;/p&gt;

&lt;p&gt;As our community and our industry mature, teams are increasingly interested in automated testing. Having worked on several Drupal projects with and without automated testing, I’ve come to the conclusion that any line of code which is not subject to automated testing &lt;em&gt;is legacy code&lt;/em&gt;; and I agree with Michael Feathers who stated in his book &lt;em&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0131177052/ref=as_li_tf_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0131177052&amp;amp;linkCode=as2&amp;amp;tag=dcycle-20&quot;&gt;Working Effectively with Legacy Code&lt;/a&gt;&lt;/em&gt;[1] that a site with zero automated tests is a legacy site from the moment you deliver it.&lt;/p&gt;

&lt;p&gt;But the road to automatic testing for Drupal is, as I’ve learned the hard way, strewn with obstacles, and first-time implementations of automated testing tend to fail. Here are a few tips to keep in mind if your team is willing to implement automated testing.&lt;/p&gt;

&lt;h2 id=&quot;tip-1-use-a-continuous-integration-server&quot;&gt;Tip #1: Use a continuous integration server&lt;/h2&gt;

&lt;p&gt;Tests are only useful if someone actually runs them. If you don’t automate running the test suite on each push to your git repo, &lt;em&gt;no one will run your tests, however good their intentions are&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The absolute first thing you need to do is set up a continuous integration (CI) server which runs a script every time your git repo changes. To make this easier I’ve set up a &lt;a href=&quot;https://github.com/alberto56/vagrant-jenkins&quot;&gt;project on GitHub&lt;/a&gt; which uses Vagrant and Puppet to set up a quick Jenkins server tailored for use with Drupal.&lt;/p&gt;

&lt;p&gt;Even before starting to write tests, make sure your continuous integration job actually runs on your master branch. When your project passes tests (which is easy at first because you won’t have tests), your project will be marked as stable.&lt;/p&gt;

&lt;p&gt;Notice that I mentioned the master branch: although git has advanced branching features, the only branch you should track in your CI server is your stable branch (often &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;, although for projects with more than one stable release, like Drupal itself, you may have two or three stable branches).&lt;/p&gt;

&lt;p&gt;It is important at this point to get the team (including the client) used to seeing the continuous integration dashboard, ideally by having a monitor in a visible place (&lt;a href=&quot;http://www.youtube.com/watch?v=3T5fEV5YHYo&quot;&gt;this team&lt;/a&gt; even plugged Jenkins into a stop light, which really grabs attention in case of a failure). If your code is flagged as failed by your CI server, you want it to be known as soon as possible, and you want the entire team to have responsibility for fixing it immediately. Your main enemy here is failure fatigue: &lt;em&gt;if your master branch is broken, and no one is working at fixing it, you will get used to seeing failures and you will fail at implementing automated testing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Eventually, you will want to add value to your continuous integration job by running &lt;a href=&quot;https://drupal.org/project/coder&quot;&gt;Code Review&lt;/a&gt; tests, and other code analysis tools like &lt;a href=&quot;http://pdepend.org&quot;&gt;Pdepend&lt;/a&gt;. With these kinds of tools, you can get a historical perspective on metrics like adherance to &lt;a href=&quot;https://drupal.org/coding-standards&quot;&gt;Drupal coding standards&lt;/a&gt;, the number of lines of code per function, code abstraction, and the like. I even like to have my Jenkins job take a screenshot of my site on every push (using &lt;a href=&quot;http://phantomjs.org&quot;&gt;PhantomJS&lt;/a&gt;), and comparing the latest screenshot to the previous one &lt;a href=&quot;http://www.imagemagick.org&quot;&gt;ImageMagick&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compare&lt;/code&gt; utility.&lt;/p&gt;

&lt;p&gt;Basically, any testing and analysis you can do on the command line should be done within your continuous integration job.&lt;/p&gt;

&lt;p&gt;If done right, and if you have high confidence in your test suite, you can eventually use your CI server to &lt;a href=&quot;http://blog.dcycle.com/blog/46&quot;&gt;deploy continuously&lt;/a&gt; to preproduction, but let’s not get ahead of ourselves.&lt;/p&gt;

&lt;h2 id=&quot;tip-2-test-your-code-not-the-database&quot;&gt;Tip #2: Test your code, not the database&lt;/h2&gt;

&lt;p&gt;Most Drupal developers I’ve talked to create their local development environment by bringing their git repo up to date, and cloning the production database.&lt;/p&gt;

&lt;p&gt;They also tend to clone the production or preproduction database back to Jenkins in their continuous integration.&lt;/p&gt;

&lt;p&gt;For me, this is the wrong approach, as I’ve &lt;a href=&quot;http://blog.dcycle.com/blog/48&quot;&gt;documented in this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basically, any tests you write should reside in your git repo and be limited to testing what’s in the git repo. If you try to test the production database, here is a typical scenario:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Someone will do something to your database which will break a test.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Your Jenkins job will clone the database, run the test, and fail.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Another person will make another change to the database, and your test will now pass.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will now see a history of failures which will indicate problems outside of your code. These will be very hard to reproduce and fix.&lt;/p&gt;

&lt;p&gt;Keep in mind that the tests you write should depend on a &lt;em&gt;known good starting point&lt;/em&gt;: you should be able to consistently reproduce an environment leading to a success or a failure. Drupal’s Simpletests completely ignore the current host database and create a new database from scratch just for testing, then destroy that database.&lt;/p&gt;

&lt;p&gt;How to do this? First, I always use a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; whose job it is to populate the database with everything that makes your site unique: enabling the site deployment module should enable all modules used by your site, and, using &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt; and related modules, deploy all views, content types, and the like, set all variables and set the default theme. The site deployment module can then be used by new developers on your team who need a development environment, and also by the CI server, &lt;em&gt;all without cloning the database&lt;/em&gt;. If you need dummy content for development, you can use &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devel_generate&lt;/code&gt; utility, along with &lt;a href=&quot;https://drupal.org/node/1748302&quot;&gt;this trick&lt;/a&gt; to make your generated content more realistic.&lt;/p&gt;

&lt;p&gt;When a bug is reported on your production site, you should reproduce it consistently in your dummy content, and then run your test against the simulation, not the real data. An example of this is the use of Wysiwyg: often, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lorem ipsum&lt;/code&gt; works fine, but once the client starts copy-pasting from Word, all kinds of problems arise. Simulated word-generated markup is the kind of thing your test should set up, and then test against.&lt;/p&gt;

&lt;p&gt;If you are involved in a highly-critical project, you might eventually want to run certain tests on a clone of your production database, but this, in my opinion, should not be attempted until you have proper test coverage and metrics for your code itself. If you do test a clone of your production database and a bug is found, reproduce the bug in a simulation, add a test to confirm the bug, and fix your code. Fixing your code to deal with a problem in production without simulating the problem first, &lt;em&gt;and testing the simulation&lt;/em&gt;, just results in more legacy code.&lt;/p&gt;

&lt;h2 id=&quot;tip-3-understand-the-effort-involved&quot;&gt;Tip #3: Understand the effort involved&lt;/h2&gt;

&lt;p&gt;Testing is time-consuming. If your client or employer asks for it, that desire needs to come with the appropriate resources. Near the beginning of a project, you can easily double all time estimates, and the payoff will come later on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/pyramid2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Stakeholders cannot expect the same velocity for a project with and without automated testing: if you are implementing testing correctly, your end-of-sprint demos will contain less features. On the other hand, once you have reached your sweet spot (see chart, above), the more manageable number of bugs will mean you can continue working on features.&lt;/p&gt;

&lt;h2 id=&quot;tip-4-start-gradually&quot;&gt;Tip #4: Start gradually&lt;/h2&gt;

&lt;p&gt;Don’t try to test everything at once. If your team is called upon to “implement automated testing” on a project, you are very likely to succumb to test paralysis if you try to implement it all at once.&lt;/p&gt;

&lt;p&gt;When working with legacy sites, or even new sites for which there is pressure to deliver fast, I have seen many teams never deliver a single test, instead delivering excuses such as “it’s really simple, we don’t need to test it”, or “we absolutely had to deliver it this week”. In reality, we tend to see “automated testing” as insurmountable and try to weasel our way of it.&lt;/p&gt;

&lt;p&gt;To overcome this, I often start a project with a single test: find a function in your code which you can run against a unit test (no database required), and write your first test. In Drupal, you can use a Simpletest Unit test (as in &lt;a href=&quot;http://blog.dcycle.com/blog/basic-unit-test&quot;&gt;this example&lt;/a&gt;) and then run it straight from the browser.&lt;/p&gt;

&lt;p&gt;Once you’re satisfied, add this line to your CI job so the test is run on every push:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush test-run mytestgroup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once that is done, it becomes easier for developers to write their own tests by adding it to the test file already present.&lt;/p&gt;

&lt;h2 id=&quot;tip-5-dont-overestimate-how-good-a-developer-you-are&quot;&gt;Tip #5: Don’t overestimate how good a developer you are&lt;/h2&gt;

&lt;p&gt;We all think we’re good developers, and really we can’t imagine anything ever going wrong with our code, I mean, &lt;em&gt;it’s so elegant!&lt;/em&gt; Well, we’re wrong.&lt;/p&gt;

&lt;p&gt;I’ve seen really intelligent people write code which looks really elegant, but still breaks.&lt;/p&gt;

&lt;p&gt;I’ve seen developers never write tests for the simple stuff because it’s too simple, and never write tests for the more complex stuff because they never practiced with the simple stuff.&lt;/p&gt;

&lt;p&gt;Even though you’re positive your code is so robust it will never break, &lt;em&gt;just test it&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;tip-6-start-with-the-low-hanging-fruit&quot;&gt;Tip #6: Start with the low-hanging fruit&lt;/h2&gt;

&lt;p&gt;This is an error I made myself and which proved very painful. Consider a system with three possible use cases for the end user. Each use case uses the same underlying calls to the database, and the same underlying &lt;a href=&quot;http://en.wikipedia.org/wiki/Pure_function&quot;&gt;pure functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, let’s say you are using a high-level testing framework like Behat and Selenium to test the rich user interface and you write three tests, one for each use case. You think (wrongly, as we’ll see) that you don’t need unit tests, because whatever it is you want to test with your unit tests &lt;em&gt;is already tested by your high-level rich user interface tests&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Don’t forget, your specs also call for you to support IE8, IE9, Webkit (Safari) and Firefox. You can set up Jenkins to run the rich GUI tests via Selenium Grid on a Windows VM, and other fancy stuff.&lt;/p&gt;

&lt;p&gt;This approach is wrong, because when you start having 5, 8, 10, 20 use cases, you will be tempted to continue just implement dozens of new, expensive rich GUI tests, and your tests will end up taking hours.&lt;/p&gt;

&lt;p&gt;In my experience, if your entire test suite takes more than two hours to run, developers will start resenting the process and ignoring the test results, and you are back to square one.&lt;/p&gt;

&lt;p&gt;In his book &lt;em&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0321579364/ref=as_li_tf_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0321579364&amp;amp;linkCode=as2&amp;amp;tag=dcycle-20&quot;&gt;Succeeding with Agile&lt;/a&gt;&lt;/em&gt;, Mike Cohn came up with the idea of a test pyramid, as shown in the diagram below (you can learn more about the concept &lt;a href=&quot;http://martinfowler.com/bliki/TestPyramid.html&quot;&gt;in this blog post&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/pyramid.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Based on this concept, we quickly realize that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Several steps are redundant among the GUI use cases.&lt;/li&gt;
  &lt;li&gt;The exact same underlying functionality is tested several times over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thinking of this from a different angle, we can start by testing our pure functions using unit tests. This will make for lightning-fast tests, and will get the team into the habit of not mixing UI functions, database functions and pure functions (for an example of what &lt;em&gt;not to do&lt;/em&gt;, see Drupal’s own &lt;a href=&quot;http://blog.dcycle.com/blog/27&quot;&gt;block_admin_display_form_submit&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Once you have built up a suite of unit tests which actually has value, move on to the next step: tests which require the database. This requires some variation of a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; or another technique to bring the database to a known-good starting point before you run the test; it is harder to grasp and setting up a CI job for these types of tests is difficult too. However, your team will more likely be willing to work hard to overcome these obstacles because of the success they achieved with unit tests.&lt;/p&gt;

&lt;p&gt;All of the above can be done with Drupal’s core &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simpletest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, when you are satisfied with your unit test suites and your database tests, you can move outside of Drupal and on targeted tests (not all usecases, only a few to make sure your widgets work) with Behat, Mink, Selenium, Windows/IE VMs. If you start with the fancy stuff, though, or have too much of it, the risk of failure is much greater.&lt;/p&gt;

&lt;h2 id=&quot;tip-7-dont-underestimate-developers-ability-to-avoid-writing-tests&quot;&gt;Tip #7: Don’t underestimate developers’ ability to avoid writing tests&lt;/h2&gt;

&lt;p&gt;If you implement all the tips you’ve seen until now in this article, something curious will happen: no one will write any tests. Not even you.&lt;/p&gt;

&lt;p&gt;Here’s the psychology behind not writing tests:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You &lt;em&gt;really&lt;/em&gt; have the intention of writing tests, you just want to get your feature working first.&lt;/li&gt;
  &lt;li&gt;You work hard at getting your feature ready for the end-of-sprint demo.&lt;/li&gt;
  &lt;li&gt;You show off your feature to the team and they like it.&lt;/li&gt;
  &lt;li&gt;You don’t write any tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above will happen to you. And keep in mind, you’re actually very interested in automated testing (enough to have read this article until now!). Now imagine your teammates, who are less interested in automated testing. They don’t stand a chance.&lt;/p&gt;

&lt;p&gt;These are some techniques to get people to write tests:&lt;/p&gt;

&lt;p&gt;The first is used by the Drupal project itself and is based on peer review of patches. If you submit a patch to core and it does not contain tests, it will not make it in. This requires that all code be reviewed before making it into your git repo’s stable branch. There are tools for this, like &lt;a href=&quot;http://phabricator.org&quot;&gt;Phabricator&lt;/a&gt;, but I’ve never successfully implemented this approach (if you have, let me know!).&lt;/p&gt;

&lt;p&gt;The second approach is to write your tests before writing a new feature or fixing a bug. This is known as &lt;em&gt;test-driven development (TDD)&lt;/em&gt; and it generally requires people to see things from a different angle. Here is a typical scenario of TDD:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A bug comes in for project xyz, and you are assigned to it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You write a test for it. If you don’t know something (no function exists yet, so you don’t know what it’s called; no field exists yet, so you don’t know how to target it), just put something feasible. If you’re dealing with the body field in your test, just use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt;. Try to test all conceivable &lt;a href=&quot;http://en.wikipedia.org/wiki/Happy_path&quot;&gt;happy paths&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; sad paths.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now switch modes: your goal is to make the test pass. This is an iterative process which entails writing code and &lt;em&gt;changing your test&lt;/em&gt; as well (your test is code too, don’t forget!). For example, perhaps the body field’s machine name is not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; but something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;field_body[&lt;/code&gt;und&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;][0]&lt;/code&gt;. If such is the case, change the test, as long as the spirit of the test remains.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above techniques, and code coverage tools like &lt;a href=&quot;https://drupal.org/project/code_coverage&quot;&gt;code_coverage&lt;/a&gt; or the experimental &lt;a href=&quot;https://drupal.org/sandbox/znerol/2004464&quot;&gt;cover&lt;/a&gt;, which I like, will help you write tests, but changing a team’s approach can only be achieved through hard work, evangelizing, presentations, blogging, and the like.&lt;/p&gt;

&lt;h2 id=&quot;tip-8-dont-subvert-your-process&quot;&gt;Tip #8: Don’t subvert your process&lt;/h2&gt;

&lt;p&gt;When it becomes challenging to write tests, you might figure that, just this once, you’ll not test something. A typical example I’ve seen of this, in project after project, is communication with outside systems and outside APIs. Because we’re not controlling the outside system, it’s hard to test it, right? True, but not impossible. If you’ve set aside enough time in your estimates to do things right, you will be able to implement &lt;a href=&quot;http://en.wikipedia.org/wiki/Mock_object&quot;&gt;mock objects&lt;/a&gt;, making sure you test everything.&lt;/p&gt;

&lt;p&gt;For example, in &lt;a href=&quot;http://blog.dcycle.com/blog/38&quot;&gt;this blog post&lt;/a&gt;, I demonstrate how I used the &lt;a href=&quot;https://drupal.org/project/mockable&quot;&gt;Mockable&lt;/a&gt; module to define mock objects to test integration between Drupal and a content deployment system.&lt;/p&gt;

&lt;p&gt;You will come across situations where implementing testing seems very hard, but however much effort I put into implementing automated testing for something, I have never regretted it.&lt;/p&gt;

&lt;h2 id=&quot;bonus-tip-the-entire-team-should-own-the-tests&quot;&gt;Bonus tip: the entire team should own the tests&lt;/h2&gt;

&lt;p&gt;Your tests cannot be imposed by any one member of the team if they are to succeed. Instead, agree on what should be tested during your sprint planning.&lt;/p&gt;

&lt;p&gt;For example, some developers (myself included) like to have close to zero Drupal styling errors. Others don’t really see the point of using two spaces instead of a tab. Unless you agree on what defines a failure (more than 100 minor styling errors? 1000? No threshold at all?), developers will feel resentful of having to fix it.&lt;/p&gt;

&lt;p&gt;Because in Agile, your client is part of team as well, it is a good idea to involve them in defining what you are testing, providing them with the costs and benefits of each test. Perhaps your client doesn’t know what a MySQL query is, but if told that keeping the number of queries to less than 100 on the home page (something that can be tracked automatically) will keep performance up, they will be more likely to accept the extra cost associated.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Automated testing is about much more than tools (often the tools are quite simple to set up). The human aspect and the methodology are much more important to get your automated testing project off the ground.&lt;/p&gt;

&lt;p&gt;[1] See Jez Humble and David Farley’s &lt;em&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0321601912/ref=as_li_tf_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0321601912&amp;amp;linkCode=as2&amp;amp;tag=dcycle-20&quot;&gt;Continuous Delivery&lt;/a&gt;&lt;/em&gt;, Addison Wesley.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;Many Drupal projects now under maintenance suffer from technical debt: a lot of the functionality is in the database and outside of git, and the code lacks automated testing. Furthermore, the functionality is often brittle: a change to one feature breaks something seemingly unrelated.&lt;/p&gt;

&lt;p&gt;As our community and our industry mature, teams are increasingly interested in automated testing. Having worked on several Drupal projects with and without automated testing, I’ve come to the conclusion that any line of code which is not subject to automated testing &lt;em&gt;is legacy code&lt;/em&gt;; and I agree with Michael Feathers who stated in his book &lt;em&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0131177052/ref=as_li_tf_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0131177052&amp;amp;linkCode=as2&amp;amp;tag=dcycle-20&quot;&gt;Working Effectively with Legacy Code&lt;/a&gt;&lt;/em&gt;[1] that a site with zero automated tests is a legacy site from the moment you deliver it.&lt;/p&gt;

&lt;p&gt;But the road to automatic testing for Drupal is, as I’ve learned the hard way, strewn with obstacles, and first-time implementations of automated testing tend to fail. Here are a few tips to keep in mind if your team is willing to implement automated testing.&lt;/p&gt;

&lt;h2 id=&quot;tip-1-use-a-continuous-integration-server&quot;&gt;Tip #1: Use a continuous integration server&lt;/h2&gt;

&lt;p&gt;Tests are only useful if someone actually runs them. If you don’t automate running the test suite on each push to your git repo, &lt;em&gt;no one will run your tests, however good their intentions are&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The absolute first thing you need to do is set up a continuous integration (CI) server which runs a script every time your git repo changes. To make this easier I’ve set up a &lt;a href=&quot;https://github.com/alberto56/vagrant-jenkins&quot;&gt;project on GitHub&lt;/a&gt; which uses Vagrant and Puppet to set up a quick Jenkins server tailored for use with Drupal.&lt;/p&gt;

&lt;p&gt;Even before starting to write tests, make sure your continuous integration job actually runs on your master branch. When your project passes tests (which is easy at first because you won’t have tests), your project will be marked as stable.&lt;/p&gt;

&lt;p&gt;Notice that I mentioned the master branch: although git has advanced branching features, the only branch you should track in your CI server is your stable branch (often &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;, although for projects with more than one stable release, like Drupal itself, you may have two or three stable branches).&lt;/p&gt;

&lt;p&gt;It is important at this point to get the team (including the client) used to seeing the continuous integration dashboard, ideally by having a monitor in a visible place (&lt;a href=&quot;http://www.youtube.com/watch?v=3T5fEV5YHYo&quot;&gt;this team&lt;/a&gt; even plugged Jenkins into a stop light, which really grabs attention in case of a failure). If your code is flagged as failed by your CI server, you want it to be known as soon as possible, and you want the entire team to have responsibility for fixing it immediately. Your main enemy here is failure fatigue: &lt;em&gt;if your master branch is broken, and no one is working at fixing it, you will get used to seeing failures and you will fail at implementing automated testing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Eventually, you will want to add value to your continuous integration job by running &lt;a href=&quot;https://drupal.org/project/coder&quot;&gt;Code Review&lt;/a&gt; tests, and other code analysis tools like &lt;a href=&quot;http://pdepend.org&quot;&gt;Pdepend&lt;/a&gt;. With these kinds of tools, you can get a historical perspective on metrics like adherance to &lt;a href=&quot;https://drupal.org/coding-standards&quot;&gt;Drupal coding standards&lt;/a&gt;, the number of lines of code per function, code abstraction, and the like. I even like to have my Jenkins job take a screenshot of my site on every push (using &lt;a href=&quot;http://phantomjs.org&quot;&gt;PhantomJS&lt;/a&gt;), and comparing the latest screenshot to the previous one &lt;a href=&quot;http://www.imagemagick.org&quot;&gt;ImageMagick&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compare&lt;/code&gt; utility.&lt;/p&gt;

&lt;p&gt;Basically, any testing and analysis you can do on the command line should be done within your continuous integration job.&lt;/p&gt;

&lt;p&gt;If done right, and if you have high confidence in your test suite, you can eventually use your CI server to &lt;a href=&quot;http://blog.dcycle.com/blog/46&quot;&gt;deploy continuously&lt;/a&gt; to preproduction, but let’s not get ahead of ourselves.&lt;/p&gt;

&lt;h2 id=&quot;tip-2-test-your-code-not-the-database&quot;&gt;Tip #2: Test your code, not the database&lt;/h2&gt;

&lt;p&gt;Most Drupal developers I’ve talked to create their local development environment by bringing their git repo up to date, and cloning the production database.&lt;/p&gt;

&lt;p&gt;They also tend to clone the production or preproduction database back to Jenkins in their continuous integration.&lt;/p&gt;

&lt;p&gt;For me, this is the wrong approach, as I’ve &lt;a href=&quot;http://blog.dcycle.com/blog/48&quot;&gt;documented in this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basically, any tests you write should reside in your git repo and be limited to testing what’s in the git repo. If you try to test the production database, here is a typical scenario:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Someone will do something to your database which will break a test.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Your Jenkins job will clone the database, run the test, and fail.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Another person will make another change to the database, and your test will now pass.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will now see a history of failures which will indicate problems outside of your code. These will be very hard to reproduce and fix.&lt;/p&gt;

&lt;p&gt;Keep in mind that the tests you write should depend on a &lt;em&gt;known good starting point&lt;/em&gt;: you should be able to consistently reproduce an environment leading to a success or a failure. Drupal’s Simpletests completely ignore the current host database and create a new database from scratch just for testing, then destroy that database.&lt;/p&gt;

&lt;p&gt;How to do this? First, I always use a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; whose job it is to populate the database with everything that makes your site unique: enabling the site deployment module should enable all modules used by your site, and, using &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt; and related modules, deploy all views, content types, and the like, set all variables and set the default theme. The site deployment module can then be used by new developers on your team who need a development environment, and also by the CI server, &lt;em&gt;all without cloning the database&lt;/em&gt;. If you need dummy content for development, you can use &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devel_generate&lt;/code&gt; utility, along with &lt;a href=&quot;https://drupal.org/node/1748302&quot;&gt;this trick&lt;/a&gt; to make your generated content more realistic.&lt;/p&gt;

&lt;p&gt;When a bug is reported on your production site, you should reproduce it consistently in your dummy content, and then run your test against the simulation, not the real data. An example of this is the use of Wysiwyg: often, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lorem ipsum&lt;/code&gt; works fine, but once the client starts copy-pasting from Word, all kinds of problems arise. Simulated word-generated markup is the kind of thing your test should set up, and then test against.&lt;/p&gt;

&lt;p&gt;If you are involved in a highly-critical project, you might eventually want to run certain tests on a clone of your production database, but this, in my opinion, should not be attempted until you have proper test coverage and metrics for your code itself. If you do test a clone of your production database and a bug is found, reproduce the bug in a simulation, add a test to confirm the bug, and fix your code. Fixing your code to deal with a problem in production without simulating the problem first, &lt;em&gt;and testing the simulation&lt;/em&gt;, just results in more legacy code.&lt;/p&gt;

&lt;h2 id=&quot;tip-3-understand-the-effort-involved&quot;&gt;Tip #3: Understand the effort involved&lt;/h2&gt;

&lt;p&gt;Testing is time-consuming. If your client or employer asks for it, that desire needs to come with the appropriate resources. Near the beginning of a project, you can easily double all time estimates, and the payoff will come later on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/pyramid2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Stakeholders cannot expect the same velocity for a project with and without automated testing: if you are implementing testing correctly, your end-of-sprint demos will contain less features. On the other hand, once you have reached your sweet spot (see chart, above), the more manageable number of bugs will mean you can continue working on features.&lt;/p&gt;

&lt;h2 id=&quot;tip-4-start-gradually&quot;&gt;Tip #4: Start gradually&lt;/h2&gt;

&lt;p&gt;Don’t try to test everything at once. If your team is called upon to “implement automated testing” on a project, you are very likely to succumb to test paralysis if you try to implement it all at once.&lt;/p&gt;

&lt;p&gt;When working with legacy sites, or even new sites for which there is pressure to deliver fast, I have seen many teams never deliver a single test, instead delivering excuses such as “it’s really simple, we don’t need to test it”, or “we absolutely had to deliver it this week”. In reality, we tend to see “automated testing” as insurmountable and try to weasel our way of it.&lt;/p&gt;

&lt;p&gt;To overcome this, I often start a project with a single test: find a function in your code which you can run against a unit test (no database required), and write your first test. In Drupal, you can use a Simpletest Unit test (as in &lt;a href=&quot;http://blog.dcycle.com/blog/basic-unit-test&quot;&gt;this example&lt;/a&gt;) and then run it straight from the browser.&lt;/p&gt;

&lt;p&gt;Once you’re satisfied, add this line to your CI job so the test is run on every push:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush test-run mytestgroup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once that is done, it becomes easier for developers to write their own tests by adding it to the test file already present.&lt;/p&gt;

&lt;h2 id=&quot;tip-5-dont-overestimate-how-good-a-developer-you-are&quot;&gt;Tip #5: Don’t overestimate how good a developer you are&lt;/h2&gt;

&lt;p&gt;We all think we’re good developers, and really we can’t imagine anything ever going wrong with our code, I mean, &lt;em&gt;it’s so elegant!&lt;/em&gt; Well, we’re wrong.&lt;/p&gt;

&lt;p&gt;I’ve seen really intelligent people write code which looks really elegant, but still breaks.&lt;/p&gt;

&lt;p&gt;I’ve seen developers never write tests for the simple stuff because it’s too simple, and never write tests for the more complex stuff because they never practiced with the simple stuff.&lt;/p&gt;

&lt;p&gt;Even though you’re positive your code is so robust it will never break, &lt;em&gt;just test it&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;tip-6-start-with-the-low-hanging-fruit&quot;&gt;Tip #6: Start with the low-hanging fruit&lt;/h2&gt;

&lt;p&gt;This is an error I made myself and which proved very painful. Consider a system with three possible use cases for the end user. Each use case uses the same underlying calls to the database, and the same underlying &lt;a href=&quot;http://en.wikipedia.org/wiki/Pure_function&quot;&gt;pure functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, let’s say you are using a high-level testing framework like Behat and Selenium to test the rich user interface and you write three tests, one for each use case. You think (wrongly, as we’ll see) that you don’t need unit tests, because whatever it is you want to test with your unit tests &lt;em&gt;is already tested by your high-level rich user interface tests&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Don’t forget, your specs also call for you to support IE8, IE9, Webkit (Safari) and Firefox. You can set up Jenkins to run the rich GUI tests via Selenium Grid on a Windows VM, and other fancy stuff.&lt;/p&gt;

&lt;p&gt;This approach is wrong, because when you start having 5, 8, 10, 20 use cases, you will be tempted to continue just implement dozens of new, expensive rich GUI tests, and your tests will end up taking hours.&lt;/p&gt;

&lt;p&gt;In my experience, if your entire test suite takes more than two hours to run, developers will start resenting the process and ignoring the test results, and you are back to square one.&lt;/p&gt;

&lt;p&gt;In his book &lt;em&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0321579364/ref=as_li_tf_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0321579364&amp;amp;linkCode=as2&amp;amp;tag=dcycle-20&quot;&gt;Succeeding with Agile&lt;/a&gt;&lt;/em&gt;, Mike Cohn came up with the idea of a test pyramid, as shown in the diagram below (you can learn more about the concept &lt;a href=&quot;http://martinfowler.com/bliki/TestPyramid.html&quot;&gt;in this blog post&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/pyramid.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Based on this concept, we quickly realize that:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Several steps are redundant among the GUI use cases.&lt;/li&gt;
  &lt;li&gt;The exact same underlying functionality is tested several times over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thinking of this from a different angle, we can start by testing our pure functions using unit tests. This will make for lightning-fast tests, and will get the team into the habit of not mixing UI functions, database functions and pure functions (for an example of what &lt;em&gt;not to do&lt;/em&gt;, see Drupal’s own &lt;a href=&quot;http://blog.dcycle.com/blog/27&quot;&gt;block_admin_display_form_submit&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Once you have built up a suite of unit tests which actually has value, move on to the next step: tests which require the database. This requires some variation of a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; or another technique to bring the database to a known-good starting point before you run the test; it is harder to grasp and setting up a CI job for these types of tests is difficult too. However, your team will more likely be willing to work hard to overcome these obstacles because of the success they achieved with unit tests.&lt;/p&gt;

&lt;p&gt;All of the above can be done with Drupal’s core &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simpletest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, when you are satisfied with your unit test suites and your database tests, you can move outside of Drupal and on targeted tests (not all usecases, only a few to make sure your widgets work) with Behat, Mink, Selenium, Windows/IE VMs. If you start with the fancy stuff, though, or have too much of it, the risk of failure is much greater.&lt;/p&gt;

&lt;h2 id=&quot;tip-7-dont-underestimate-developers-ability-to-avoid-writing-tests&quot;&gt;Tip #7: Don’t underestimate developers’ ability to avoid writing tests&lt;/h2&gt;

&lt;p&gt;If you implement all the tips you’ve seen until now in this article, something curious will happen: no one will write any tests. Not even you.&lt;/p&gt;

&lt;p&gt;Here’s the psychology behind not writing tests:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You &lt;em&gt;really&lt;/em&gt; have the intention of writing tests, you just want to get your feature working first.&lt;/li&gt;
  &lt;li&gt;You work hard at getting your feature ready for the end-of-sprint demo.&lt;/li&gt;
  &lt;li&gt;You show off your feature to the team and they like it.&lt;/li&gt;
  &lt;li&gt;You don’t write any tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above will happen to you. And keep in mind, you’re actually very interested in automated testing (enough to have read this article until now!). Now imagine your teammates, who are less interested in automated testing. They don’t stand a chance.&lt;/p&gt;

&lt;p&gt;These are some techniques to get people to write tests:&lt;/p&gt;

&lt;p&gt;The first is used by the Drupal project itself and is based on peer review of patches. If you submit a patch to core and it does not contain tests, it will not make it in. This requires that all code be reviewed before making it into your git repo’s stable branch. There are tools for this, like &lt;a href=&quot;http://phabricator.org&quot;&gt;Phabricator&lt;/a&gt;, but I’ve never successfully implemented this approach (if you have, let me know!).&lt;/p&gt;

&lt;p&gt;The second approach is to write your tests before writing a new feature or fixing a bug. This is known as &lt;em&gt;test-driven development (TDD)&lt;/em&gt; and it generally requires people to see things from a different angle. Here is a typical scenario of TDD:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A bug comes in for project xyz, and you are assigned to it.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You write a test for it. If you don’t know something (no function exists yet, so you don’t know what it’s called; no field exists yet, so you don’t know how to target it), just put something feasible. If you’re dealing with the body field in your test, just use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt;. Try to test all conceivable &lt;a href=&quot;http://en.wikipedia.org/wiki/Happy_path&quot;&gt;happy paths&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; sad paths.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now switch modes: your goal is to make the test pass. This is an iterative process which entails writing code and &lt;em&gt;changing your test&lt;/em&gt; as well (your test is code too, don’t forget!). For example, perhaps the body field’s machine name is not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;body&lt;/code&gt; but something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;field_body[&lt;/code&gt;und&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;][0]&lt;/code&gt;. If such is the case, change the test, as long as the spirit of the test remains.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above techniques, and code coverage tools like &lt;a href=&quot;https://drupal.org/project/code_coverage&quot;&gt;code_coverage&lt;/a&gt; or the experimental &lt;a href=&quot;https://drupal.org/sandbox/znerol/2004464&quot;&gt;cover&lt;/a&gt;, which I like, will help you write tests, but changing a team’s approach can only be achieved through hard work, evangelizing, presentations, blogging, and the like.&lt;/p&gt;

&lt;h2 id=&quot;tip-8-dont-subvert-your-process&quot;&gt;Tip #8: Don’t subvert your process&lt;/h2&gt;

&lt;p&gt;When it becomes challenging to write tests, you might figure that, just this once, you’ll not test something. A typical example I’ve seen of this, in project after project, is communication with outside systems and outside APIs. Because we’re not controlling the outside system, it’s hard to test it, right? True, but not impossible. If you’ve set aside enough time in your estimates to do things right, you will be able to implement &lt;a href=&quot;http://en.wikipedia.org/wiki/Mock_object&quot;&gt;mock objects&lt;/a&gt;, making sure you test everything.&lt;/p&gt;

&lt;p&gt;For example, in &lt;a href=&quot;http://blog.dcycle.com/blog/38&quot;&gt;this blog post&lt;/a&gt;, I demonstrate how I used the &lt;a href=&quot;https://drupal.org/project/mockable&quot;&gt;Mockable&lt;/a&gt; module to define mock objects to test integration between Drupal and a content deployment system.&lt;/p&gt;

&lt;p&gt;You will come across situations where implementing testing seems very hard, but however much effort I put into implementing automated testing for something, I have never regretted it.&lt;/p&gt;

&lt;h2 id=&quot;bonus-tip-the-entire-team-should-own-the-tests&quot;&gt;Bonus tip: the entire team should own the tests&lt;/h2&gt;

&lt;p&gt;Your tests cannot be imposed by any one member of the team if they are to succeed. Instead, agree on what should be tested during your sprint planning.&lt;/p&gt;

&lt;p&gt;For example, some developers (myself included) like to have close to zero Drupal styling errors. Others don’t really see the point of using two spaces instead of a tab. Unless you agree on what defines a failure (more than 100 minor styling errors? 1000? No threshold at all?), developers will feel resentful of having to fix it.&lt;/p&gt;

&lt;p&gt;Because in Agile, your client is part of team as well, it is a good idea to involve them in defining what you are testing, providing them with the costs and benefits of each test. Perhaps your client doesn’t know what a MySQL query is, but if told that keeping the number of queries to less than 100 on the home page (something that can be tracked automatically) will keep performance up, they will be more likely to accept the extra cost associated.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Automated testing is about much more than tools (often the tools are quite simple to set up). The human aspect and the methodology are much more important to get your automated testing project off the ground.&lt;/p&gt;

&lt;p&gt;[1] See Jez Humble and David Farley’s &lt;em&gt;&lt;a href=&quot;http://www.amazon.com/gp/product/0321601912/ref=as_li_tf_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=0321601912&amp;amp;linkCode=as2&amp;amp;tag=dcycle-20&quot;&gt;Continuous Delivery&lt;/a&gt;&lt;/em&gt;, Addison Wesley.&lt;/p&gt;
</description>
        
        <pubDate>Wed, 26 Feb 2014 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/52/eight-tips-remember-your-path-automated-testing/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/52/eight-tips-remember-your-path-automated-testing/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Do not use incremental IDs in your code</title>
        <description>&lt;p&gt;Drupal uses incremental IDs for such data as taxonomy terms and nodes, but not content types or vocabularies. If, like me, you believe your site’s codebase should work with different environments and different databases, your incremental IDs can be different on each environment, causing your code to break.&lt;/p&gt;

&lt;p&gt;But wait, you are thinking, I have only one environment: my production environment.&lt;/p&gt;

&lt;p&gt;Even if such is the case, there are advantages to be able to spawn new environments independently of the production environment &lt;a href=&quot;http://blog.dcycle.com/blog/48&quot;&gt;without cloning the database upstream&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Everything you need to create your website, minus the content, is under version control. The production database, being outside version control, should not be needed to install a new environment. See also “&lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;what is a deployment module?&lt;/a&gt;”.&lt;/li&gt;
  &lt;li&gt;New developers can be up and running with a predictable environment and dummy content.&lt;/li&gt;
  &lt;li&gt;Your automated tests, using Drupal’s Simpletest, by default deploy a new environment without cloning the database.&lt;/li&gt;
  &lt;li&gt;For predictable results in your continuous integration server, it is best to deploy a new envrionment. The production database is unpredictable and unversioned. If you test it, your test results will be unpredictable as well.&lt;/li&gt;
  &lt;li&gt;Maybe in the future you’ll need a separate version of your site with different data (for a new market, perhaps).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you choose to clone the database upstream for development, testing and continuous integration, it is still a good idea to avoid referencing incremental IDs of a particular database, because at some point you might decide that it is important to be able to have environments with different databases.&lt;/p&gt;

&lt;h2 id=&quot;example-1-using-node-ids-in-css-and-in-template-files&quot;&gt;Example #1: using node IDs in CSS and in template files&lt;/h2&gt;

&lt;p&gt;I have often seen this: particular pages (say, nodes 56 and 400) require particular markup, so we see template files like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page--node--56.tpl.php&lt;/code&gt; and css like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.page-node-56 #content,
.page-node-400 #content {
   ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When, as developers, we decide to use this type of code on a website, we are tightly coupling our code, which is under version control, to our database, which is not under version control. In other words our project as a whole can no longer be said to be versioned as it requires a database clone to work correctly.&lt;/p&gt;

&lt;p&gt;Also, this creates all sorts of problems: if, for example, a new node needs to be created which has the same characteristics as nodes 56 and 400, one must fiddle with the database (to create the node) &lt;em&gt;and&lt;/em&gt; the code. Also, creating automatic tests for something like this is hard because the approach is not based on underlying logic.&lt;/p&gt;

&lt;p&gt;A better approach to this problem might be to figure out &lt;em&gt;why&lt;/em&gt; nodes 56 and 400 are somehow different than the others. The solution will depend on your answer to that question, and maybe these nodes need to be of a different content type; or maybe some other mechanism should be used. In all cases, though, their ID should be irrelevant to their specificity.&lt;/p&gt;

&lt;h2 id=&quot;example-2-filtering-a-view-by-taxonomy-tag&quot;&gt;Example #2: filtering a view by taxonomy tag&lt;/h2&gt;

&lt;p&gt;You might have a website which uses Drupal’s default implementation of articles, with a tag taxonomy field. You might decide that all articles tagged with “blog” should appear in your blog, and you might create a new view, filtered to display all articles with the “blog” tag.&lt;/p&gt;

&lt;p&gt;Now, you might export your view into a &lt;a href=&quot;https://drupal.org/project/features&quot;&gt;feature&lt;/a&gt; and, perhaps, make your feature a dependency of a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; (so that enabling this module on a new environment will deploy your blog feature, and do everything else necessary to make your site unique, such as enabling the default theme, etc.).&lt;/p&gt;

&lt;p&gt;It is important to understand that with this approach, you are in effect putting an incremental ID into code. You view is in fact filtering by the &lt;em&gt;ID of the “blog” taxonomy term as it happens to exist on the site used to create the view&lt;/em&gt;. When creating the view, we have no idea what this ID is, but we are saying that in order for our view to work, the “blog” taxonomy term needs to be identical on all environments.&lt;/p&gt;

&lt;p&gt;Here is an example of how this bug will play out:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;This being the most important feature of your site, when creating new environments, the “blog” taxonomy term might always have the ID 1 because it is the first taxonomy term created; you might also be in the habit of cloning your database for new environments, in which case the problem will remain latent.&lt;/li&gt;
  &lt;li&gt;You might decide that such a feature is too “simple” to warrant automated testing; but even if you do define an automated test, your test will run on a new database and will need to create the “blog” taxonomy term in order to validate. Because your tests are separate and simple, the “blog” taxonomy term is probably the only term created during testing, so it, too will have ID 1, and thus your test will pass.&lt;/li&gt;
  &lt;li&gt;Your continuous integration server which monitors changes to your versioned code will run tests against every push, but, again, on a new database, so your tests will pass and your code will be fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This might go on for quite some time until, on a given environment, someone decides to create another term &lt;em&gt;before&lt;/em&gt; creating the “blog” term. Now the “blog” term will have ID #2 which will break your feature.&lt;/p&gt;

&lt;p&gt;Consider, furthermore, that your client decides to create a new view for “jobs” and use the same tag mechanism as for the blog; and perhaps other tags as well. Before long, your entire development cycle becomes dependent on database cloning to work properly.&lt;/p&gt;

&lt;p&gt;To come up with a better approach, it is important to understand what we are trying to accomplish; and what taxonomy terms are meant to be used for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The “blog” category here is somehow, logically, immutable and means something very specific. Furthermore, the existence of the blog category is required for our site. Even if its name changes, the &lt;em&gt;key&lt;/em&gt; (or underlying identity) of the blog category should always be the same.&lt;/li&gt;
  &lt;li&gt;Taxonomy terms are referenced with incremental IDs (like nodes) and thus, when writing our code, their IDs (and even their existence) cannot be counted upon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, we are using taxonomy terms for the wrong purpose. Taxonomy terms, like nodes, are meant to be potentially different for each environment: &lt;em&gt;our code should not depend on them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A potential solution in this case would be to create a new field for articles, perhaps a multiple selection field, with “blog” as one of the possible values. Now, when we create a view filtered by the value “blog” in our new field, we are no longer referencing an incremental ID in our code.&lt;/p&gt;

&lt;p&gt;I myself made this very mistake with my own website code without realizing it. The code for this website (the one you are reading) is available on Github and the issue for this problem is &lt;a href=&quot;https://github.com/alberto56/dcyclesite/issues/3&quot;&gt;documented here&lt;/a&gt; (I’ll try to get around to fixing it soon!).&lt;/p&gt;

&lt;h2 id=&quot;deploying-a-fix-to-an-existing-site&quot;&gt;Deploying a fix to an existing site&lt;/h2&gt;

&lt;p&gt;If you apply these practices from the start of a project, it is relatively straightforward. However, what if a site is already in production with several articles already labelled “blog” (as is the case on the Dcycle website itself)? In this case we need to incrementally deploy the fix. For this, a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; can be of use: in your site deployment module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file, you can add a new update hook to update all your existing articles labelled “blog”, something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Use a machine name rather than an incremental ID to display blog items.
 */
function mysite_deploy_update_7010() {
  // deploy the new version of the view to the target site
  features_revert(array(&apos;mysite_feature&apos; =&amp;gt; array(&apos;views_view&apos;)));
  ...
  // cycle through your nodes and add &quot;blog&quot; to your new field for any
  // content labelled &quot;blog&quot;.
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, you need to test this first with a clone of your production site, perhaps even adding an automatic test to make sure your function works as expected. Also, if you have &lt;em&gt;a lot&lt;/em&gt; of nodes, you might need to use the “sandbox” feature of &lt;a href=&quot;https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_update_N/7&quot;&gt;hook_update_n()&lt;/a&gt;, to avoid timeouts.&lt;/p&gt;

&lt;p&gt;Once all is tested, all that needs to be done, on each environment (production, every developer’s laptop, etc.), is run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; on the command line.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Drupal makes it very easy to mix incremental IDs into views and code, and this will work well if you always use the same database on every environment. However, you will quickly run into problems if you want to write automated tests or deploy new sites without cloning the database. Being aware of this can help you write more logical, consistent and predictable code.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;Drupal uses incremental IDs for such data as taxonomy terms and nodes, but not content types or vocabularies. If, like me, you believe your site’s codebase should work with different environments and different databases, your incremental IDs can be different on each environment, causing your code to break.&lt;/p&gt;

&lt;p&gt;But wait, you are thinking, I have only one environment: my production environment.&lt;/p&gt;

&lt;p&gt;Even if such is the case, there are advantages to be able to spawn new environments independently of the production environment &lt;a href=&quot;http://blog.dcycle.com/blog/48&quot;&gt;without cloning the database upstream&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Everything you need to create your website, minus the content, is under version control. The production database, being outside version control, should not be needed to install a new environment. See also “&lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;what is a deployment module?&lt;/a&gt;”.&lt;/li&gt;
  &lt;li&gt;New developers can be up and running with a predictable environment and dummy content.&lt;/li&gt;
  &lt;li&gt;Your automated tests, using Drupal’s Simpletest, by default deploy a new environment without cloning the database.&lt;/li&gt;
  &lt;li&gt;For predictable results in your continuous integration server, it is best to deploy a new envrionment. The production database is unpredictable and unversioned. If you test it, your test results will be unpredictable as well.&lt;/li&gt;
  &lt;li&gt;Maybe in the future you’ll need a separate version of your site with different data (for a new market, perhaps).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you choose to clone the database upstream for development, testing and continuous integration, it is still a good idea to avoid referencing incremental IDs of a particular database, because at some point you might decide that it is important to be able to have environments with different databases.&lt;/p&gt;

&lt;h2 id=&quot;example-1-using-node-ids-in-css-and-in-template-files&quot;&gt;Example #1: using node IDs in CSS and in template files&lt;/h2&gt;

&lt;p&gt;I have often seen this: particular pages (say, nodes 56 and 400) require particular markup, so we see template files like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;page--node--56.tpl.php&lt;/code&gt; and css like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.page-node-56 #content,
.page-node-400 #content {
   ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When, as developers, we decide to use this type of code on a website, we are tightly coupling our code, which is under version control, to our database, which is not under version control. In other words our project as a whole can no longer be said to be versioned as it requires a database clone to work correctly.&lt;/p&gt;

&lt;p&gt;Also, this creates all sorts of problems: if, for example, a new node needs to be created which has the same characteristics as nodes 56 and 400, one must fiddle with the database (to create the node) &lt;em&gt;and&lt;/em&gt; the code. Also, creating automatic tests for something like this is hard because the approach is not based on underlying logic.&lt;/p&gt;

&lt;p&gt;A better approach to this problem might be to figure out &lt;em&gt;why&lt;/em&gt; nodes 56 and 400 are somehow different than the others. The solution will depend on your answer to that question, and maybe these nodes need to be of a different content type; or maybe some other mechanism should be used. In all cases, though, their ID should be irrelevant to their specificity.&lt;/p&gt;

&lt;h2 id=&quot;example-2-filtering-a-view-by-taxonomy-tag&quot;&gt;Example #2: filtering a view by taxonomy tag&lt;/h2&gt;

&lt;p&gt;You might have a website which uses Drupal’s default implementation of articles, with a tag taxonomy field. You might decide that all articles tagged with “blog” should appear in your blog, and you might create a new view, filtered to display all articles with the “blog” tag.&lt;/p&gt;

&lt;p&gt;Now, you might export your view into a &lt;a href=&quot;https://drupal.org/project/features&quot;&gt;feature&lt;/a&gt; and, perhaps, make your feature a dependency of a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; (so that enabling this module on a new environment will deploy your blog feature, and do everything else necessary to make your site unique, such as enabling the default theme, etc.).&lt;/p&gt;

&lt;p&gt;It is important to understand that with this approach, you are in effect putting an incremental ID into code. You view is in fact filtering by the &lt;em&gt;ID of the “blog” taxonomy term as it happens to exist on the site used to create the view&lt;/em&gt;. When creating the view, we have no idea what this ID is, but we are saying that in order for our view to work, the “blog” taxonomy term needs to be identical on all environments.&lt;/p&gt;

&lt;p&gt;Here is an example of how this bug will play out:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;This being the most important feature of your site, when creating new environments, the “blog” taxonomy term might always have the ID 1 because it is the first taxonomy term created; you might also be in the habit of cloning your database for new environments, in which case the problem will remain latent.&lt;/li&gt;
  &lt;li&gt;You might decide that such a feature is too “simple” to warrant automated testing; but even if you do define an automated test, your test will run on a new database and will need to create the “blog” taxonomy term in order to validate. Because your tests are separate and simple, the “blog” taxonomy term is probably the only term created during testing, so it, too will have ID 1, and thus your test will pass.&lt;/li&gt;
  &lt;li&gt;Your continuous integration server which monitors changes to your versioned code will run tests against every push, but, again, on a new database, so your tests will pass and your code will be fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This might go on for quite some time until, on a given environment, someone decides to create another term &lt;em&gt;before&lt;/em&gt; creating the “blog” term. Now the “blog” term will have ID #2 which will break your feature.&lt;/p&gt;

&lt;p&gt;Consider, furthermore, that your client decides to create a new view for “jobs” and use the same tag mechanism as for the blog; and perhaps other tags as well. Before long, your entire development cycle becomes dependent on database cloning to work properly.&lt;/p&gt;

&lt;p&gt;To come up with a better approach, it is important to understand what we are trying to accomplish; and what taxonomy terms are meant to be used for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The “blog” category here is somehow, logically, immutable and means something very specific. Furthermore, the existence of the blog category is required for our site. Even if its name changes, the &lt;em&gt;key&lt;/em&gt; (or underlying identity) of the blog category should always be the same.&lt;/li&gt;
  &lt;li&gt;Taxonomy terms are referenced with incremental IDs (like nodes) and thus, when writing our code, their IDs (and even their existence) cannot be counted upon.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, we are using taxonomy terms for the wrong purpose. Taxonomy terms, like nodes, are meant to be potentially different for each environment: &lt;em&gt;our code should not depend on them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A potential solution in this case would be to create a new field for articles, perhaps a multiple selection field, with “blog” as one of the possible values. Now, when we create a view filtered by the value “blog” in our new field, we are no longer referencing an incremental ID in our code.&lt;/p&gt;

&lt;p&gt;I myself made this very mistake with my own website code without realizing it. The code for this website (the one you are reading) is available on Github and the issue for this problem is &lt;a href=&quot;https://github.com/alberto56/dcyclesite/issues/3&quot;&gt;documented here&lt;/a&gt; (I’ll try to get around to fixing it soon!).&lt;/p&gt;

&lt;h2 id=&quot;deploying-a-fix-to-an-existing-site&quot;&gt;Deploying a fix to an existing site&lt;/h2&gt;

&lt;p&gt;If you apply these practices from the start of a project, it is relatively straightforward. However, what if a site is already in production with several articles already labelled “blog” (as is the case on the Dcycle website itself)? In this case we need to incrementally deploy the fix. For this, a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; can be of use: in your site deployment module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file, you can add a new update hook to update all your existing articles labelled “blog”, something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Use a machine name rather than an incremental ID to display blog items.
 */
function mysite_deploy_update_7010() {
  // deploy the new version of the view to the target site
  features_revert(array(&apos;mysite_feature&apos; =&amp;gt; array(&apos;views_view&apos;)));
  ...
  // cycle through your nodes and add &quot;blog&quot; to your new field for any
  // content labelled &quot;blog&quot;.
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, you need to test this first with a clone of your production site, perhaps even adding an automatic test to make sure your function works as expected. Also, if you have &lt;em&gt;a lot&lt;/em&gt; of nodes, you might need to use the “sandbox” feature of &lt;a href=&quot;https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_update_N/7&quot;&gt;hook_update_n()&lt;/a&gt;, to avoid timeouts.&lt;/p&gt;

&lt;p&gt;Once all is tested, all that needs to be done, on each environment (production, every developer’s laptop, etc.), is run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; on the command line.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Drupal makes it very easy to mix incremental IDs into views and code, and this will work well if you always use the same database on every environment. However, you will quickly run into problems if you want to write automated tests or deploy new sites without cloning the database. Being aware of this can help you write more logical, consistent and predictable code.&lt;/p&gt;
</description>
        
        <pubDate>Mon, 20 Jan 2014 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/50/do-not-use-incremental-ids-your-code/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/50/do-not-use-incremental-ids-your-code/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Do not clone the database</title>
        <description>&lt;p&gt;It is generally agreed that cloning the database downstream (that is, from development toward production) is a bad idea, if only because by doing so all production content is lost; most developers use &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt;, &lt;a href=&quot;http://drupal.org/project/context&quot;&gt;Context&lt;/a&gt;, some variation on a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;, or a rudimentary written procedure to move new configuration downstream.&lt;/p&gt;

&lt;p&gt;However, in a dev-stage-production workflow, the database is often still periodically cloned back upstream:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/environment_flow.png&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In such an approach, anything not in &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt; or a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; exists solely in the database. For example: any content, your default theme, and other information (such as variables not exported with &lt;a href=&quot;http://drupal.org/project/strongarm&quot;&gt;Strongarm&lt;/a&gt; or block placement information not exported with &lt;a href=&quot;http://drupal.org/project/context&quot;&gt;Context&lt;/a&gt;) are defined only in your database and not in code. Therefore, to create a realistic development environment, it is tempting to clone your database.&lt;/p&gt;

&lt;p&gt;I’ll explain why I think database cloning is the wrong approach, and then look at other ways to achieve the same goals. Finally, I’ll look at some situations where cloning the database is a good idea.&lt;/p&gt;

&lt;h2 id=&quot;why-is-cloning-the-database-the-wrong-approach&quot;&gt;Why is cloning the database the wrong approach?&lt;/h2&gt;

&lt;p&gt;Cloning the database is wrong for the following reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The database is not under version control.&lt;/li&gt;
  &lt;li&gt;The database is not a known-good starting point.&lt;/li&gt;
  &lt;li&gt;Database cloning makes automated testing harder.&lt;/li&gt;
  &lt;li&gt;Database cloning makes continuous integration harder.&lt;/li&gt;
  &lt;li&gt;What if there is more than one “production” site?&lt;/li&gt;
  &lt;li&gt;Your production database may be very large.&lt;/li&gt;
  &lt;li&gt;Your production database may contain sensitive data.&lt;/li&gt;
  &lt;li&gt;Fixes to a cloned database will “work on my machine”, but not elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;the-database-is-not-under-version-control&quot;&gt;The database is not under version control&lt;/h3&gt;

&lt;p&gt;In Drupal, the database contains all configuration, content types, variables, views, and content; and none of this is under version control.&lt;/p&gt;

&lt;p&gt;A good development practice is to put everything &lt;em&gt;except content&lt;/em&gt; into code, and into version control, via Features, Context, Strongarm, and a site deployment module. These are code and can be kept under version control.&lt;/p&gt;

&lt;h3 id=&quot;the-database-is-not-a-known-good-starting-point&quot;&gt;The database is not a known-good starting point&lt;/h3&gt;

&lt;p&gt;One important aspect of writing modern software is the importance of automated testing, and the importance of &lt;em&gt;knowing that our test will always yield the same result&lt;/em&gt;. This is the concept of a known good starting point, discussed in the book &lt;a href=&quot;http://www.amazon.com/Continuous-Delivery-Deployment-Automation-Addison-Wesley/dp/0321601912&quot;&gt;Continuous Delivery&lt;/a&gt;. The production database changes continually, for example when new comments or content are added. If your tests, either manual or automated, depend on a cloned production database, there is always a chance that different versions of the database will be yield different test results.&lt;/p&gt;

&lt;h3 id=&quot;database-cloning-makes-automated-testing-harder&quot;&gt;Database cloning makes automated testing harder&lt;/h3&gt;

&lt;p&gt;Because of the importance of having a known-good starting point, Drupal automated tests which require the database always work in the following manner:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Build a brand-new temporary (throw-away) database &lt;em&gt;from scratch&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;Perform a plain installation.&lt;/li&gt;
  &lt;li&gt;Create the required content and set the required configuration.&lt;/li&gt;
  &lt;li&gt;Perform the test.&lt;/li&gt;
  &lt;li&gt;Discard the throw-away database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, let’s say you have a block appear when there are more than 20 registered users on your site. The only way to accurately test this is to have your test control the number of users, and test the presence or absence of your block. If the only way to deploy a new environment with your site is to clone the database, the test has no real way of creating the conditions (active theme, block placement, active modules) to run this test.&lt;/p&gt;

&lt;p&gt;However, if you are using Features and a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;, all your tests needs to do for the above example is to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;(1) Enable your site deployment module.&lt;/li&gt;
  &lt;li&gt;(2) Make sure the special block does not appear.&lt;/li&gt;
  &lt;li&gt;(3) Create the 20 users.&lt;/li&gt;
  &lt;li&gt;(4) Make sure the block does appear.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;database-cloning-makes-continuous-integration-harder&quot;&gt;Database cloning makes continuous integration harder&lt;/h3&gt;

&lt;p&gt;Continuous integration (CI) and continuous deployment are quite popular these days, with good reason, because without CI, automated testing is not that useful (because developers tend to ignore tests).&lt;/p&gt;

&lt;p&gt;Basically, CI runs a script on every push to version control. So: every time there is a change to the code base, the tests can be run and either pass or fail.&lt;/p&gt;

&lt;p&gt;I have seen many shops experiment with continuous integration, and in many cases the Drupal site is recreated by cloning the production database. Therefore, the CI server’s test site is always in an unknown, unversioned state. So when a test fails, it is impossible to say whether a change to the database caused the fail, or a change to the code did.&lt;/p&gt;

&lt;p&gt;In my experience this causes frustration and confusion, and eventually will cause your CI server to be worthless, and hence abandoned.&lt;/p&gt;

&lt;h3 id=&quot;what-if-there-is-more-than-one-production-site&quot;&gt;What if there is more than one “production” site?&lt;/h3&gt;

&lt;p&gt;When we are cloning the production site’s database, what do we mean exactly? Take the following example: we are developing a code base for a university with dozens of faculties. Each faculty uses the same code base but a different theme, and some slightly different settings.&lt;/p&gt;

&lt;p&gt;It doesn’t make sense for new developers to clone one production database rather than another for development, so often a random choice is made, leading to uncertainty.&lt;/p&gt;

&lt;p&gt;Consider your codebase to be a software product which can be deployed on any number of sites, just as any software. Would it make sense for developers of a word processor to clone the computer of one of their clients during routine development?&lt;/p&gt;

&lt;h3 id=&quot;your-production-database-may-be-very-large&quot;&gt;Your production database may be very large&lt;/h3&gt;

&lt;p&gt;Beyond the logical considerations, cloning production databases can be unwieldy, requiring one to remove cache tables, finding a mechanism to either copy all files and images, ignore them, or use placeholder files and images (that does not feel right, no?). Still, you can quickly find yourself with very large databases.&lt;/p&gt;

&lt;h3 id=&quot;your-production-database-may-contain-sensitive-data&quot;&gt;Your production database may contain sensitive data&lt;/h3&gt;

&lt;p&gt;Once your production site actually starts being used, you end up with much sensitive data there: email addresses, hashed passwords, order history, addresses, or worse. Consider the consequences if you dump this database on a developer’s laptop (which will eventually be stolen or lost).&lt;/p&gt;

&lt;h3 id=&quot;fixes-to-a-cloned-database-will-work-on-my-machine-but-not-elsewhere&quot;&gt;Fixes to a cloned database will “work on my machine”, but not elsewhere&lt;/h3&gt;

&lt;p&gt;So you’ve cloned a database on your laptop, and you changed some configuration on administration pages, and now the problem seems fixed, you’ve made a demo for your team and your client. The next part is messy though: a list of admin screens to click through on the production site to reproduce the fix (ugh!), or, as I’ve already seen, cloning the development database &lt;em&gt;downstream&lt;/em&gt; (double-ugh!). Both methods are error-prone and do not record the fix in version control, so a month from now you’ll forget how it was done. In fact, you will find yourself in a sysyphian effort of repeatedly fixing the same problem over and over, and explaining to your clients and your team, with the help of out-of-date wiki pages, email exchanges and undecipherable comments on issue queues, that you are not an incompetent oaf.&lt;/p&gt;

&lt;h2 id=&quot;what-are-the-alternatives-to-database-cloning&quot;&gt;What are the alternatives to database cloning?&lt;/h2&gt;

&lt;p&gt;We generally clone the database to have a realistic development environment. Among other things, during development, we need to have:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The same configuration and features.&lt;/li&gt;
  &lt;li&gt;Realistic content.&lt;/li&gt;
  &lt;li&gt;Some exact problem-causing content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is possible without cloning the database. Here are some tips and techniques.&lt;/p&gt;

&lt;h3 id=&quot;getting-the-same-configuration-and-features-as-production&quot;&gt;Getting the same configuration and features as production&lt;/h3&gt;

&lt;p&gt;In an ideal world any Drupal site should be deployable without cloning the database, by getting the code from git and enabling the &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You are most likely, however, to inherit a site which is a mess: no site deployment module, no tests, with &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt;, if they exist at all, likely to be overridden on the production site. On some projects you’d be lucky to even have a git repo.&lt;/p&gt;

&lt;p&gt;One might think that for such sites, which we’ll call legacy sites for the purpose of this article, cloning the production database is the only viable option. Unfortunately, that is true, but it should only be a temporary solution, to give you time to extract the important configuration into code, and to create a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s say, for example, I get a work request to “fix a little bug on a site which is almost ready”. The first thing I do is to clone the entire site to my laptop, with the database and all, and and determine which configurations, features, and variables are affected by the bug. Let’s say the site in question has 20 content types, 20 views, 50 enabled modules, three languages and a custom theme.&lt;/p&gt;

&lt;p&gt;But the bug in question only affects 2 content types, one view, 3 modules and does not require the custom theme or i18n. I would start by generating a feature (if one does not exist) with the required views and content types, and a site deployment module with the feature as a dependency and a &lt;a href=&quot;http://blog.dcycle.com/blog/30&quot;&gt;basic automated test&lt;/a&gt;. Now I can use test-driven development to fix my bug, push everything back to version control and to my continuous integration server, and deploy to production using drush.&lt;/p&gt;

&lt;p&gt;Thus, every time an issue is being worked on, a site gradually moves from being a legacy site to a modern, tested site with continuous integration (don’t do it all at once as you will get discouraged).&lt;/p&gt;

&lt;h3 id=&quot;realistic-content&quot;&gt;Realistic content&lt;/h3&gt;

&lt;p&gt;For developers, &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devel_generate&lt;/code&gt; module is great for generating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lorem ipsum&lt;/code&gt; content with dummy images, so even if you don’t clone your database, you can still get, say, 50 (or 1000) blog posts with 5 (or 50) comments each.&lt;/p&gt;

&lt;p&gt;During automated testing, several &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21simpletest%21drupal_web_test_case.php/class/DrupalWebTestCase/7&quot;&gt;DrupalWebTestCase API functions&lt;/a&gt; allow you to create as much dummy content as you want, being as specific as you want.&lt;/p&gt;

&lt;h3 id=&quot;some-exact-problem-causing-content&quot;&gt;Some exact problem-causing content&lt;/h3&gt;

&lt;p&gt;I have recently had to deal with a bug where the a site’s “layout was periodically going berserk”. That was the exact issue title, and I was lucky because my client was thoughtful enough to provide a screenshot and even the source code.&lt;/p&gt;

&lt;p&gt;This problem could be tracked down to a often-seen misconfiguration of views and marked-up content: views would &lt;em&gt;trim&lt;/em&gt; all body fields to 100 characters, which works fine with standard lorem ipsum, but in the real world the client was using markup in the content, so if a &amp;lt;div&amp;gt; tag would appear before the 100 character mark, but end after it, the ending tag would be omitted, screwing up the html.&lt;/p&gt;

&lt;p&gt;Several colleagues who are used to cloning the database concluded that this a limitation of generated content.&lt;/p&gt;

&lt;p&gt;I see this situation as more of an opportunity, and have come up with &lt;a href=&quot;https://drupal.org/comment/7834865#comment-7834865&quot;&gt;a way of altering generated lorum ipsum&lt;/a&gt; to suit your needs. So, when starting to work on such an issue, first make sure that your generated content better reflects real content, both for developers and for the automated tests.&lt;/p&gt;

&lt;h2 id=&quot;when-is-it-ok-to-clone-the-database&quot;&gt;When is it OK to clone the database?&lt;/h2&gt;

&lt;p&gt;“&lt;em&gt;Don’t clone the database&lt;/em&gt;” is a good rule of thumb, but in some cases &lt;a href=&quot;http://blog.dcycle.com/blog/33&quot;&gt;cloning the database&lt;/a&gt; is good idea, for example in the following cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;For backups and restores.&lt;/li&gt;
  &lt;li&gt;For hard-to-debug “production-only” problems.&lt;/li&gt;
  &lt;li&gt;As a temporary measure to update a legacy site.&lt;/li&gt;
  &lt;li&gt;For proproduction environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;for-backups-and-restores&quot;&gt;For backups and restores&lt;/h3&gt;

&lt;p&gt;Code is not everything. The database contains your content, so you need to have a strategy to clone your database somewhere nightly, test it often, and make sure you can restore it. This is mot easily done by cloning the database.&lt;/p&gt;

&lt;h3 id=&quot;for-hard-to-debug-production-only-problems&quot;&gt;For hard-to-debug “production-only” problems&lt;/h3&gt;

&lt;p&gt;Once in a while, you will have a problem which only manifests itself on a production site. Reproducing this type of problem systematically can be best achieved by cloning your production database to figure out what the problem is (never work directly on production, of course).&lt;/p&gt;

&lt;h3 id=&quot;as-a-temporary-measure-to-update-a-legacy-site&quot;&gt;As a temporary measure to update a legacy site&lt;/h3&gt;

&lt;p&gt;As mentioned in &lt;em&gt;“Getting the same configuration and features as production”&lt;/em&gt;, above, most projects are a complete mess once you get your hands on them. We’ll call these legacy sites. The only way to move important configuration information into code is often to clone these sites temporarily until you have working &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt; and a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;for-proproduction-environments&quot;&gt;For proproduction environments&lt;/h3&gt;

&lt;p&gt;For some critical projects, you might decide to &lt;a href=&quot;http://blog.dcycle.com/blog/46&quot;&gt;continually deploy&lt;/a&gt;, but not directly to production. In such circumstances, you might have your Jenkins projects continually deploy to a preproduction site (cloned from production before each deployment), to give the team, and the client, a few hours or a day to walk through the changes before approving them for deployment to production.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Since being interested in Drupal dev-stage-prod, deployment and testing, I have often come across colleagues who systematically cloned the database, and have always felt uneasy about it, and in writing this post I have set out to explain why. The post turned out &lt;em&gt;a lot&lt;/em&gt; longer than I thought, and the main take-away is that we should all consider our sites as software products, not single-use sites.&lt;/p&gt;

&lt;p&gt;As software products, we need standardized deployment methods, both initial and incremental, via a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As software products, we also need to implement modern testing and continuous integration techniques.&lt;/p&gt;

&lt;p&gt;As software products, we need to be able to deploy anywhere, with no environment dependant on any other.&lt;/p&gt;

&lt;p&gt;Such a focus on reproducibility will hopefully pave the way to more dependable tests, a better understanding of what is content and what is configuration, and faster, more efficient and more consistent development.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;It is generally agreed that cloning the database downstream (that is, from development toward production) is a bad idea, if only because by doing so all production content is lost; most developers use &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt;, &lt;a href=&quot;http://drupal.org/project/context&quot;&gt;Context&lt;/a&gt;, some variation on a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;, or a rudimentary written procedure to move new configuration downstream.&lt;/p&gt;

&lt;p&gt;However, in a dev-stage-production workflow, the database is often still periodically cloned back upstream:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://blog.dcycle.com/sites/blog.dcycle.com/files/environment_flow.png&quot; style=&quot;width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In such an approach, anything not in &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt; or a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; exists solely in the database. For example: any content, your default theme, and other information (such as variables not exported with &lt;a href=&quot;http://drupal.org/project/strongarm&quot;&gt;Strongarm&lt;/a&gt; or block placement information not exported with &lt;a href=&quot;http://drupal.org/project/context&quot;&gt;Context&lt;/a&gt;) are defined only in your database and not in code. Therefore, to create a realistic development environment, it is tempting to clone your database.&lt;/p&gt;

&lt;p&gt;I’ll explain why I think database cloning is the wrong approach, and then look at other ways to achieve the same goals. Finally, I’ll look at some situations where cloning the database is a good idea.&lt;/p&gt;

&lt;h2 id=&quot;why-is-cloning-the-database-the-wrong-approach&quot;&gt;Why is cloning the database the wrong approach?&lt;/h2&gt;

&lt;p&gt;Cloning the database is wrong for the following reasons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The database is not under version control.&lt;/li&gt;
  &lt;li&gt;The database is not a known-good starting point.&lt;/li&gt;
  &lt;li&gt;Database cloning makes automated testing harder.&lt;/li&gt;
  &lt;li&gt;Database cloning makes continuous integration harder.&lt;/li&gt;
  &lt;li&gt;What if there is more than one “production” site?&lt;/li&gt;
  &lt;li&gt;Your production database may be very large.&lt;/li&gt;
  &lt;li&gt;Your production database may contain sensitive data.&lt;/li&gt;
  &lt;li&gt;Fixes to a cloned database will “work on my machine”, but not elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;the-database-is-not-under-version-control&quot;&gt;The database is not under version control&lt;/h3&gt;

&lt;p&gt;In Drupal, the database contains all configuration, content types, variables, views, and content; and none of this is under version control.&lt;/p&gt;

&lt;p&gt;A good development practice is to put everything &lt;em&gt;except content&lt;/em&gt; into code, and into version control, via Features, Context, Strongarm, and a site deployment module. These are code and can be kept under version control.&lt;/p&gt;

&lt;h3 id=&quot;the-database-is-not-a-known-good-starting-point&quot;&gt;The database is not a known-good starting point&lt;/h3&gt;

&lt;p&gt;One important aspect of writing modern software is the importance of automated testing, and the importance of &lt;em&gt;knowing that our test will always yield the same result&lt;/em&gt;. This is the concept of a known good starting point, discussed in the book &lt;a href=&quot;http://www.amazon.com/Continuous-Delivery-Deployment-Automation-Addison-Wesley/dp/0321601912&quot;&gt;Continuous Delivery&lt;/a&gt;. The production database changes continually, for example when new comments or content are added. If your tests, either manual or automated, depend on a cloned production database, there is always a chance that different versions of the database will be yield different test results.&lt;/p&gt;

&lt;h3 id=&quot;database-cloning-makes-automated-testing-harder&quot;&gt;Database cloning makes automated testing harder&lt;/h3&gt;

&lt;p&gt;Because of the importance of having a known-good starting point, Drupal automated tests which require the database always work in the following manner:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Build a brand-new temporary (throw-away) database &lt;em&gt;from scratch&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;Perform a plain installation.&lt;/li&gt;
  &lt;li&gt;Create the required content and set the required configuration.&lt;/li&gt;
  &lt;li&gt;Perform the test.&lt;/li&gt;
  &lt;li&gt;Discard the throw-away database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, let’s say you have a block appear when there are more than 20 registered users on your site. The only way to accurately test this is to have your test control the number of users, and test the presence or absence of your block. If the only way to deploy a new environment with your site is to clone the database, the test has no real way of creating the conditions (active theme, block placement, active modules) to run this test.&lt;/p&gt;

&lt;p&gt;However, if you are using Features and a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;, all your tests needs to do for the above example is to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;(1) Enable your site deployment module.&lt;/li&gt;
  &lt;li&gt;(2) Make sure the special block does not appear.&lt;/li&gt;
  &lt;li&gt;(3) Create the 20 users.&lt;/li&gt;
  &lt;li&gt;(4) Make sure the block does appear.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;database-cloning-makes-continuous-integration-harder&quot;&gt;Database cloning makes continuous integration harder&lt;/h3&gt;

&lt;p&gt;Continuous integration (CI) and continuous deployment are quite popular these days, with good reason, because without CI, automated testing is not that useful (because developers tend to ignore tests).&lt;/p&gt;

&lt;p&gt;Basically, CI runs a script on every push to version control. So: every time there is a change to the code base, the tests can be run and either pass or fail.&lt;/p&gt;

&lt;p&gt;I have seen many shops experiment with continuous integration, and in many cases the Drupal site is recreated by cloning the production database. Therefore, the CI server’s test site is always in an unknown, unversioned state. So when a test fails, it is impossible to say whether a change to the database caused the fail, or a change to the code did.&lt;/p&gt;

&lt;p&gt;In my experience this causes frustration and confusion, and eventually will cause your CI server to be worthless, and hence abandoned.&lt;/p&gt;

&lt;h3 id=&quot;what-if-there-is-more-than-one-production-site&quot;&gt;What if there is more than one “production” site?&lt;/h3&gt;

&lt;p&gt;When we are cloning the production site’s database, what do we mean exactly? Take the following example: we are developing a code base for a university with dozens of faculties. Each faculty uses the same code base but a different theme, and some slightly different settings.&lt;/p&gt;

&lt;p&gt;It doesn’t make sense for new developers to clone one production database rather than another for development, so often a random choice is made, leading to uncertainty.&lt;/p&gt;

&lt;p&gt;Consider your codebase to be a software product which can be deployed on any number of sites, just as any software. Would it make sense for developers of a word processor to clone the computer of one of their clients during routine development?&lt;/p&gt;

&lt;h3 id=&quot;your-production-database-may-be-very-large&quot;&gt;Your production database may be very large&lt;/h3&gt;

&lt;p&gt;Beyond the logical considerations, cloning production databases can be unwieldy, requiring one to remove cache tables, finding a mechanism to either copy all files and images, ignore them, or use placeholder files and images (that does not feel right, no?). Still, you can quickly find yourself with very large databases.&lt;/p&gt;

&lt;h3 id=&quot;your-production-database-may-contain-sensitive-data&quot;&gt;Your production database may contain sensitive data&lt;/h3&gt;

&lt;p&gt;Once your production site actually starts being used, you end up with much sensitive data there: email addresses, hashed passwords, order history, addresses, or worse. Consider the consequences if you dump this database on a developer’s laptop (which will eventually be stolen or lost).&lt;/p&gt;

&lt;h3 id=&quot;fixes-to-a-cloned-database-will-work-on-my-machine-but-not-elsewhere&quot;&gt;Fixes to a cloned database will “work on my machine”, but not elsewhere&lt;/h3&gt;

&lt;p&gt;So you’ve cloned a database on your laptop, and you changed some configuration on administration pages, and now the problem seems fixed, you’ve made a demo for your team and your client. The next part is messy though: a list of admin screens to click through on the production site to reproduce the fix (ugh!), or, as I’ve already seen, cloning the development database &lt;em&gt;downstream&lt;/em&gt; (double-ugh!). Both methods are error-prone and do not record the fix in version control, so a month from now you’ll forget how it was done. In fact, you will find yourself in a sysyphian effort of repeatedly fixing the same problem over and over, and explaining to your clients and your team, with the help of out-of-date wiki pages, email exchanges and undecipherable comments on issue queues, that you are not an incompetent oaf.&lt;/p&gt;

&lt;h2 id=&quot;what-are-the-alternatives-to-database-cloning&quot;&gt;What are the alternatives to database cloning?&lt;/h2&gt;

&lt;p&gt;We generally clone the database to have a realistic development environment. Among other things, during development, we need to have:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The same configuration and features.&lt;/li&gt;
  &lt;li&gt;Realistic content.&lt;/li&gt;
  &lt;li&gt;Some exact problem-causing content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is possible without cloning the database. Here are some tips and techniques.&lt;/p&gt;

&lt;h3 id=&quot;getting-the-same-configuration-and-features-as-production&quot;&gt;Getting the same configuration and features as production&lt;/h3&gt;

&lt;p&gt;In an ideal world any Drupal site should be deployable without cloning the database, by getting the code from git and enabling the &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You are most likely, however, to inherit a site which is a mess: no site deployment module, no tests, with &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt;, if they exist at all, likely to be overridden on the production site. On some projects you’d be lucky to even have a git repo.&lt;/p&gt;

&lt;p&gt;One might think that for such sites, which we’ll call legacy sites for the purpose of this article, cloning the production database is the only viable option. Unfortunately, that is true, but it should only be a temporary solution, to give you time to extract the important configuration into code, and to create a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s say, for example, I get a work request to “fix a little bug on a site which is almost ready”. The first thing I do is to clone the entire site to my laptop, with the database and all, and and determine which configurations, features, and variables are affected by the bug. Let’s say the site in question has 20 content types, 20 views, 50 enabled modules, three languages and a custom theme.&lt;/p&gt;

&lt;p&gt;But the bug in question only affects 2 content types, one view, 3 modules and does not require the custom theme or i18n. I would start by generating a feature (if one does not exist) with the required views and content types, and a site deployment module with the feature as a dependency and a &lt;a href=&quot;http://blog.dcycle.com/blog/30&quot;&gt;basic automated test&lt;/a&gt;. Now I can use test-driven development to fix my bug, push everything back to version control and to my continuous integration server, and deploy to production using drush.&lt;/p&gt;

&lt;p&gt;Thus, every time an issue is being worked on, a site gradually moves from being a legacy site to a modern, tested site with continuous integration (don’t do it all at once as you will get discouraged).&lt;/p&gt;

&lt;h3 id=&quot;realistic-content&quot;&gt;Realistic content&lt;/h3&gt;

&lt;p&gt;For developers, &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;devel_generate&lt;/code&gt; module is great for generating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lorem ipsum&lt;/code&gt; content with dummy images, so even if you don’t clone your database, you can still get, say, 50 (or 1000) blog posts with 5 (or 50) comments each.&lt;/p&gt;

&lt;p&gt;During automated testing, several &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21simpletest%21drupal_web_test_case.php/class/DrupalWebTestCase/7&quot;&gt;DrupalWebTestCase API functions&lt;/a&gt; allow you to create as much dummy content as you want, being as specific as you want.&lt;/p&gt;

&lt;h3 id=&quot;some-exact-problem-causing-content&quot;&gt;Some exact problem-causing content&lt;/h3&gt;

&lt;p&gt;I have recently had to deal with a bug where the a site’s “layout was periodically going berserk”. That was the exact issue title, and I was lucky because my client was thoughtful enough to provide a screenshot and even the source code.&lt;/p&gt;

&lt;p&gt;This problem could be tracked down to a often-seen misconfiguration of views and marked-up content: views would &lt;em&gt;trim&lt;/em&gt; all body fields to 100 characters, which works fine with standard lorem ipsum, but in the real world the client was using markup in the content, so if a &amp;lt;div&amp;gt; tag would appear before the 100 character mark, but end after it, the ending tag would be omitted, screwing up the html.&lt;/p&gt;

&lt;p&gt;Several colleagues who are used to cloning the database concluded that this a limitation of generated content.&lt;/p&gt;

&lt;p&gt;I see this situation as more of an opportunity, and have come up with &lt;a href=&quot;https://drupal.org/comment/7834865#comment-7834865&quot;&gt;a way of altering generated lorum ipsum&lt;/a&gt; to suit your needs. So, when starting to work on such an issue, first make sure that your generated content better reflects real content, both for developers and for the automated tests.&lt;/p&gt;

&lt;h2 id=&quot;when-is-it-ok-to-clone-the-database&quot;&gt;When is it OK to clone the database?&lt;/h2&gt;

&lt;p&gt;“&lt;em&gt;Don’t clone the database&lt;/em&gt;” is a good rule of thumb, but in some cases &lt;a href=&quot;http://blog.dcycle.com/blog/33&quot;&gt;cloning the database&lt;/a&gt; is good idea, for example in the following cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;For backups and restores.&lt;/li&gt;
  &lt;li&gt;For hard-to-debug “production-only” problems.&lt;/li&gt;
  &lt;li&gt;As a temporary measure to update a legacy site.&lt;/li&gt;
  &lt;li&gt;For proproduction environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;for-backups-and-restores&quot;&gt;For backups and restores&lt;/h3&gt;

&lt;p&gt;Code is not everything. The database contains your content, so you need to have a strategy to clone your database somewhere nightly, test it often, and make sure you can restore it. This is mot easily done by cloning the database.&lt;/p&gt;

&lt;h3 id=&quot;for-hard-to-debug-production-only-problems&quot;&gt;For hard-to-debug “production-only” problems&lt;/h3&gt;

&lt;p&gt;Once in a while, you will have a problem which only manifests itself on a production site. Reproducing this type of problem systematically can be best achieved by cloning your production database to figure out what the problem is (never work directly on production, of course).&lt;/p&gt;

&lt;h3 id=&quot;as-a-temporary-measure-to-update-a-legacy-site&quot;&gt;As a temporary measure to update a legacy site&lt;/h3&gt;

&lt;p&gt;As mentioned in &lt;em&gt;“Getting the same configuration and features as production”&lt;/em&gt;, above, most projects are a complete mess once you get your hands on them. We’ll call these legacy sites. The only way to move important configuration information into code is often to clone these sites temporarily until you have working &lt;a href=&quot;http://drupal.org/project/features&quot;&gt;Features&lt;/a&gt; and a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;for-proproduction-environments&quot;&gt;For proproduction environments&lt;/h3&gt;

&lt;p&gt;For some critical projects, you might decide to &lt;a href=&quot;http://blog.dcycle.com/blog/46&quot;&gt;continually deploy&lt;/a&gt;, but not directly to production. In such circumstances, you might have your Jenkins projects continually deploy to a preproduction site (cloned from production before each deployment), to give the team, and the client, a few hours or a day to walk through the changes before approving them for deployment to production.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Since being interested in Drupal dev-stage-prod, deployment and testing, I have often come across colleagues who systematically cloned the database, and have always felt uneasy about it, and in writing this post I have set out to explain why. The post turned out &lt;em&gt;a lot&lt;/em&gt; longer than I thought, and the main take-away is that we should all consider our sites as software products, not single-use sites.&lt;/p&gt;

&lt;p&gt;As software products, we need standardized deployment methods, both initial and incremental, via a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As software products, we also need to implement modern testing and continuous integration techniques.&lt;/p&gt;

&lt;p&gt;As software products, we need to be able to deploy anywhere, with no environment dependant on any other.&lt;/p&gt;

&lt;p&gt;Such a focus on reproducibility will hopefully pave the way to more dependable tests, a better understanding of what is content and what is configuration, and faster, more efficient and more consistent development.&lt;/p&gt;
</description>
        
        <pubDate>Tue, 07 Jan 2014 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/48/do-not-clone-database/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/48/do-not-clone-database/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Continuous deployment, Drupal style</title>
        <description>&lt;p&gt;Edit (2016-10-03): &lt;a href=&quot;http://blog.dcycle.com/blog/2016-10-02/when-not-to-use-drupal/&quot;&gt;This website is no longer Drupal-based&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Deployments are often one of the most pain-inducing aspects of the Drupal development cycle. I have talked to Drupal developers in several shops, and have found that best practices are often ignored in favor of cloning databases downstream, manually reproducing content on prod environments, following a series of error-prone manual steps on each environment, and other bad practices, all of which should be thrown out the door.&lt;/p&gt;

&lt;p&gt;In this article I am referring to deployment of site configuration (not content) on a Drupal 7 site. Configuration refers such aspects of your site as the default theme, CSS aggregation status, content types, views, vocabularies, and the like.&lt;/p&gt;

&lt;p&gt;Taking best practices to the extreme, it is possible to deploy &lt;em&gt;continually&lt;/em&gt;, dozens of times a day, automatically. The following procedure is a proof of concept, and you will probably want to adapt it to your needs, introducing a manual step perhaps, if only to make sure your deployments happen on fixed schedule.&lt;/p&gt;

&lt;p&gt;Still, I have started using the exact procedure discussed herein to deploy the website you are currently reading, &lt;a href=&quot;http://blog.dcycle.com&quot;&gt;blog.dcycle.com&lt;/a&gt;. Furthermore, &lt;a href=&quot;https://github.com/alberto56/dcyclesite&quot;&gt;the code is on Github&lt;/a&gt;, so anyone can reproduce the dcycle project site, without the content (I’ll detail how later on).&lt;/p&gt;

&lt;h1 id=&quot;initial-goal&quot;&gt;Initial goal&lt;/h1&gt;

&lt;p&gt;In an effort to demonstrate that the principles of the &lt;a href=&quot;http://blog.dcycle.com/&quot;&gt;Dcycle manifesto&lt;/a&gt; work well for a real — albeit simple — website, I have started to deploy changes to the Dcycle website itself via a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; with automatic testing, a continuous integration server (Jenkins), and continuous deployment. In fact, if you visit &lt;a href=&quot;http://blog.dcycle.com/&quot;&gt;the dcycle website&lt;/a&gt;, you might come across a maintenance page. This is a deployment in action, and chances are it’s happening automatically.&lt;/p&gt;

&lt;p&gt;So what is continuous deployment? For our purposes, it is a site development method which follows these principles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The site’s code is under version control (we are using &lt;a href=&quot;http://git-scm.com&quot;&gt;Git&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;Our site is deployed, initially and incrementally, via a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Automatic testing confirms that the features we have developed actually work.&lt;/li&gt;
  &lt;li&gt;Two branches exist: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;, on which development occurs, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt;, considered stable, on which all tests have passed.&lt;/li&gt;
  &lt;li&gt;A continuous integration server (in our case &lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt;) monitors the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch, and moves code to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch only if automated tests pass.&lt;/li&gt;
  &lt;li&gt;The production site is never changed directly, but via a job in the continuous integration server.&lt;/li&gt;
  &lt;li&gt;Once the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch is updated, so is the production site.&lt;/li&gt;
  &lt;li&gt;Databases are never cloned, except to move legacy sites to your local development environment. (A legacy site, in the context of this article, is a site which you can’t deploy (minus the content) without cloning the database).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical workflow happens like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Code is committed and pushed to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch in the git repo.&lt;/li&gt;
  &lt;li&gt;Jenkins picks up on the change, runs the tests, and they fail. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch and the production site are untouched.&lt;/li&gt;
  &lt;li&gt;The problem leading to the failing test is fixed, and the code is pushed, again to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch in the git repo.&lt;/li&gt;
  &lt;li&gt;Jenkins picks up on the change, runs the tests, and this time they pass. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch is updated automatically, and the production site itself is updated. &lt;em&gt;Automatically&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;tools&quot;&gt;Tools&lt;/h1&gt;

&lt;p&gt;Before getting started, make sure you have the following.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A continuous integration server, which can be on your laptop if you wish. &lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt; is easy to set up, and took me five minutes to install on Mac OS, and another five minutes on CentOS. Just follow the instructions.&lt;/li&gt;
  &lt;li&gt;A central git repo. You can fork &lt;a href=&quot;https://github.com/alberto56/dcyclesite&quot;&gt;the code for the blog.dcycle.com website&lt;/a&gt; if you like.&lt;/li&gt;
  &lt;li&gt;A webserver on your laptop. I am using &lt;a href=&quot;http://www.mamp.info/en/index.html&quot;&gt;MAMP&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Access to your production website on the command line via SSH.&lt;/li&gt;
  &lt;li&gt;SSH public-private key access to the production server, to avoid being asked for passwords. This is important for Jenkins to modify the production server automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We won’t be using git hooks or Drupal’s GUI.&lt;/p&gt;

&lt;h1 id=&quot;step-one-pick-an-issue&quot;&gt;Step one: pick an issue&lt;/h1&gt;

&lt;p&gt;More often than not, we are working on &lt;em&gt;existing&lt;/em&gt; Drupal sites, not new ones, and we don’t have the luxury of redeveloping everything with best practices. So we’ll start with a single issue, either a bug or feature request. Here is a real-life example for the &lt;a href=&quot;http://blog.dcycle.com&quot;&gt;Dcycle website&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;I like the idea of each article having its ID reflected in the URL, as is the case with &lt;a href=&quot;http://stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt;. I want the path of my articles to be in the format &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/12345/title-of-the-post&lt;/code&gt;. I also want it to be possible to shorten the path and have it redirect the full path, so for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/12345&lt;/code&gt; redirects to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/12345/title-of-the-post&lt;/code&gt;, as is the case on Stack Overflow.&lt;/p&gt;

&lt;p&gt;So, I started out with the goal of implementing this feature using continuous deployment and automated tests.&lt;/p&gt;

&lt;h1 id=&quot;step-two-create-a-local-version-of-the-website&quot;&gt;Step two: create a local version of the website&lt;/h1&gt;

&lt;p&gt;If your site has a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; or something like it, download your code from git and deploy the site locally, using these commands, substituting your own site deployment module name and database credentials for those in the example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo &apos;create database example&apos; | mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/example --account-name=root --account-pass=root
drush en example_deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to try this at home and create a local version of the &lt;a href=&quot;http://blog.dcycle.com&quot;&gt;Dcycle website&lt;/a&gt;, make sure you have a webserver, PHP and MySQL installed, and run the following commands (if you want to actually modify the code, fork it first and use your project URL instead of mine). This example uses MAMP.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd /Applications/MAMP/htdocs
git clone https://github.com/alberto56/dcyclesite.git dcyclesample
cd dcyclesample
echo &apos;create database dcyclesample&apos; | mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/dcyclesample --account-name=root --account-pass=root
drush en dcycle_deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above will yield an empty website. Adding some generated content will make development easier:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en devel_generate -y
drush generate-content 50
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If there is no site deployment site, you can &lt;a href=&quot;http://blog.dcycle.com/blog/33&quot;&gt;clone the database&lt;/a&gt;, but don’t make a habit of it!&lt;/p&gt;

&lt;h1 id=&quot;step-three-make-sure-you-have-a-site-deployment-module&quot;&gt;Step three: make sure you have a site deployment module&lt;/h1&gt;

&lt;p&gt;To work well with continuous deployment, your site needs to have a consistent way of being initially and incrementally deployed. To achieve this, I recommend the use of a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create one for your site (one already exists for the Dcycle website code, if you are using that), and make sure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file contains everything necessary to deploy your site. To make sure initial deployment and incremental deployment result in the same state, I just call all my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt; hooks from my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;install&lt;/code&gt; hook, and that has worked fine for me. Your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file might look something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @file
 * sites/default/modules/custom/dcycle_deploy/dcycle_deploy.install
 * Initial and incremental deployment of this website.
 */

/**
 * Implements hook_install().
 */
function dcycle_deploy_install() {
  for ($i = 7001; $i &amp;lt; 8000; $i++) {
    $candidate = &apos;dcycle_deploy_update_&apos; . $i;
    if (function_exists($candidate)) {
      $candidate();
    }
  }
}

/**
 * Admin menu
 */
function dcycle_deploy_update_7007() {
  module_enable(array(&apos;admin_menu_toolbar&apos;));
  module_disable(array(&apos;toolbar&apos;));
}

...

/**
 * Set dark_elegant as theme
 */
function dcycle_deploy_update_7015() {
  theme_enable(array(&apos;dark_elegant&apos;));
  variable_set(&apos;theme_default&apos;, &apos;dark_elegant&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above code sets the default theme and changes the toolbar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin_menu_toolbar&lt;/code&gt;, which I prefer.&lt;/p&gt;

&lt;p&gt;Because these features were deployed at different times (the theme was changed after the toolbar was changed), &lt;a href=&quot;https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_update_N/7&quot;&gt;numbered update hooks&lt;/a&gt; are used.&lt;/p&gt;

&lt;p&gt;Notice how the install hook cycles through all the update hooks, ensuring that our initial deployment and incremental deployments result in the same state. For any given environment, now, regardless of the previous state, bringing it up to date is simply a matter of updating the database. The following script can now be used on any environment:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush vset maintenance_mode 1
drush updb -y
drush cc all
drush vset maintenance_mode 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above puts the site in maintenance mode, runs all the update hooks which have not yet been run, clears the caches and takes the site out of maintenance mode.&lt;/p&gt;

&lt;p&gt;We now have standardized deployment, both initial and incremental.&lt;/p&gt;

&lt;h1 id=&quot;step-four-write-a-failing-test&quot;&gt;Step four: write a failing test&lt;/h1&gt;

&lt;p&gt;For continuous deployment to be of any use, we need to have very high confidence in our tests. A good first step to that end is for our tests to actually exist. And a good way to ensure that your tests exist is to write them before anything else. This is &lt;a href=&quot;http://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;Test-driven development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you cloned my git repo for this site, the “short path” feature, introduced above, has already been implemented and tested, so the test passes. Still, here is the code I had written, which initially was failing. You might want to write something similar, or add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test...()&lt;/code&gt; function to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.test&lt;/code&gt; file, for another feature.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @file
 * sites/default/modules/custom/dcycle_deploy/dcycle_deploy.test
 * This file contains the testing code for this module
 */

// Test should run with this number of blog posts.
define(&apos;DCYCLE_DEPLOY_TEST_BLOG_COUNT&apos;, 5);

/**
 * The test case
 */
class dcyclesiteTestCase extends DrupalWebTestCase {
  /**
   * Info for this test case.
   */
  public static function getInfo() {
    return array(
      &apos;name&apos; =&amp;gt; t(&apos;dcyclesite: basic test&apos;),
      &apos;description&apos; =&amp;gt; t(&apos;describe test.&apos;),
      &apos;group&apos; =&amp;gt; &apos;dcyclesite&apos;,
    );
  }

  /*
   * Enable your module
   */
  public function setUp() {
    // set up a new site with default core modules, dcyclesite, and
    // dependencies.
    parent::setUp(&apos;dcycle_deploy&apos;);
  }

  /*
   * Test case for dcyclesite.
   */
  public function testModule() {
    $this-&amp;gt;loginAsRole(&apos;administrator&apos;);
    $blogs = array();
    for ($i = 1; $i &amp;lt;= DCYCLE_DEPLOY_TEST_BLOG_COUNT; $i++) {
      $this-&amp;gt;drupalCreateNode(array(&apos;type&apos; =&amp;gt; &apos;article&apos;, &apos;title&apos; =&amp;gt; &apos;É&apos; . $blogs[$i] = $this-&amp;gt;randomName()));
      foreach (array(&apos;blog&apos;, &apos;node&apos;) as $base) {
        // passing alias =&amp;gt; TRUE, otherwise, the test converts our call
        // to the alias before the query.
        $this-&amp;gt;drupalGet($base . &apos;/&apos; . $i, array(&apos;alias&apos; =&amp;gt; TRUE));
        // assertUrl() does not work here, because the current url (node/1)
        // equals node/1 and equals also its alias. We want it to equal its
        // alias only.
        $url = $this-&amp;gt;getUrl();
        global $base_url;
        $expected = $base_url . &apos;/blog/&apos; . $i . &apos;/e&apos; . strtolower($blogs[$i]);
        $this-&amp;gt;assertEqual($url, $expected , format_string(&apos;Blog can be accessed using @base/x and will redirect correctly because the end url (@url) is equal to @expected.&apos;, array(&apos;@base&apos; =&amp;gt; $base, &apos;@url&apos; =&amp;gt; $url, &apos;@expected&apos; =&amp;gt; $expected)));
      }
    }
  }

  /*
   * Login as administrator role.
   *
   * This can be a useful for tests in your deployment module, especially
   * if you create several roles in a Feature dependency.
   *
   * @param $role = &apos;administrator&apos;
   *   Log in as any role, or administrator by default.
   */
  public function loginAsRole($role = &apos;administrator&apos;) {
    // Get all of the roles in the system.
    $roles = user_roles();
    // Find the index for the role we want to assign to the user.
    $index = array_search($role, $roles);
    // Get the permissions for the role.
    $permissions = user_role_permissions(array(array_search($role, $roles) =&amp;gt; $role));
    // Create the user with the permissions.
    $user = $this-&amp;gt;drupalCreateUser(array_keys($permissions[$index]));
    // Assign the role.
    $user-&amp;gt;roles[$index] = $role;
    // Log in as this user
    if (!($user = user_save($user))) {
      throw new Exception(format_string(&apos;cannot save user with role @r&apos;, array(&apos;@r&apos; =&amp;gt; $role)));
    }
    $this-&amp;gt;drupalLogin($user);
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Don’t forget to reference your test in your .info file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
files[] = dcycle_deploy.test
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What are we doing in the automatic test, above?&lt;/p&gt;

&lt;p&gt;Take a look at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setUp()&lt;/code&gt; function, which does everything required to create a new environment of this website. Because we have used a site deployment module, “everything” is simply a matter of enabling that module.&lt;/p&gt;

&lt;p&gt;The key here is that whether the scenario works or not on any given environment (local, prod, etc.) is irrelevant: it needs to work based on a &lt;em&gt;known good starting point&lt;/em&gt;. Databases are moving targets and it is thus irrelevant to test your code against an existing database (except if you want to monitor your production environment, which is a more advanced use case and outside the scope of this article). Therefore, we need to bring the throw-away testing database to a point where we can test run a test, and &lt;em&gt;know that our test will always yield the same result&lt;/em&gt;. The concept of a known good starting point is discussed in the book &lt;a href=&quot;http://www.amazon.com/Continuous-Delivery-Deployment-Automation-Addison-Wesley/dp/0321601912&quot;&gt;Continuous Delivery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Given a new throw-away environment, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testModule()&lt;/code&gt; now runs the test, defining a scenario which should work: we are logging in as an administrator, creating new blog posts (making sure to use foreign characters in the title), and then making sure the foreign characters are transliterated to ASCII characters and that our content redirects correctly when using only the ID. Let’s enable Simpletest now and make sure our test is visible and fails:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en simpletest -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now log into your site, and visit admin/config/development/testing, and run your test. If the desired functionality has not yet been developed, your test should fail.&lt;/p&gt;

&lt;h1 id=&quot;step-five-make-sure-the-test-passes&quot;&gt;Step five: make sure the test passes&lt;/h1&gt;

&lt;p&gt;At this point let’s switch gears and focus our energy on making our test pass. This normally involves several code iterations, and running the test dozens of times, until it passes.&lt;/p&gt;

&lt;p&gt;An important note for test-driven development: the initial test is an approximation, and may have to be modified during coding. The &lt;em&gt;spirit&lt;/em&gt; of the test, as opposed to the &lt;em&gt;letter&lt;/em&gt; of the test, should be conserved.&lt;/p&gt;

&lt;p&gt;Test-driven development has the interesting side effect that it makes it easier for teams to collaborate: if I am working with a team in a different time zone, it is less error-prone for me to instruct them to “make the test work on branch xyz and then merge it to master”, rather than explain everything I have in mind.&lt;/p&gt;

&lt;p&gt;In the case of the task at hand, I wrote some custom code in a new module, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dcyclesite&lt;/code&gt;, and then enabled some new modules and configuration. Don’t forget, all operations which modify the database have to be done in update hooks. Here is a partial example of how my site deployment module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file looks after I made the test pass:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Enable some custom code
 */
function dcycle_deploy_update_7022() {
  # this is where my custom code is; check my github code if you
  # are curious.
  module_enable(array(&apos;dcyclesite&apos;));
}

/**
 * Pattern for articles
 */
function dcycle_deploy_update_7023() {
  variable_set(&apos;pathauto_node_article_pattern&apos;, &apos;blog/[node:nid]/[node:title]&apos;);
}

/**
 * Enable transliteration
 */
function dcycle_deploy_update_7024() {
  module_enable(array(&apos;transliteration&apos;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;step-six-make-the-test-work-in-the-command-line&quot;&gt;Step six: make the test work &lt;em&gt;in the command line&lt;/em&gt;&lt;/h1&gt;

&lt;p&gt;Coming back to continuous deployment, we need our tests to be run every time code is pushed to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch, and running our tests will eventually be done by our Jenkins server.&lt;/p&gt;

&lt;p&gt;The idea behind Jenkins is quite simple: run a script as a reaction to an event. The event is a change in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch. Jenkins, though, does not know how to fiddle around in a GUI. Therefore we must make it possible to run the tests in the command line. Fortunately this is quite easy:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush test-run dcyclesite
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above runs all tests in the test group “dcyclesite”. Change “dcyclesite” to whatever your group name is. For tests to run correctly from the command line, you must make sure you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base_url&lt;/code&gt; is set correctly in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/default/settings.php&lt;/code&gt; file. This depends on your environment but must reflect the URL at which your site is accessible, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$base_url = &apos;http://localhost:8888/mywebsite&apos;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush test-run dcyclesite&lt;/code&gt; should fail if there is a failing test. I normally develop code using Simpletest in the GUI, and use the command-line for regression testing in Jenkins.&lt;/p&gt;

&lt;h1 id=&quot;step-seven-create-a-jenkins-job&quot;&gt;Step seven: create a Jenkins job&lt;/h1&gt;

&lt;p&gt;Now the fun starts: create a Jenkins job to continuously deploy. Simply put, we need our jenkins server to monitor the master branch of git, and if everything passes, move our code to the production branch, and, if we are feeling particularly confident in our tests, deploy to the production site.&lt;/p&gt;

&lt;p&gt;Jenkins is one of the easiest pieces of software I have ever installed. You can probably get a CI server up and running in a matter of minutes by downloading the appropriate package on the &lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once that is done, set up Jenkins:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jenkins&lt;/code&gt; user on your Jenkins server has an SSH key pair, and has public-private SSH key access to both the git repo and the production server. Note that this will allow anyone with access to your Jenkins server to access your production server and your git repo, so apply security best practices to your Jenkins server!&lt;/li&gt;
  &lt;li&gt;In Jenkins’s plugin manager page, install &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin&quot;&gt;the Git plugin&lt;/a&gt; and the &lt;a href=&quot;http://wiki.hudson-ci.org/display/HUDSON/Post+build+task&quot;&gt;post-build task plugin&lt;/a&gt;, which allows you to add a second script if the first script succeeds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now create a single job with the following attributes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Source code management: git.&lt;/li&gt;
  &lt;li&gt;Source code repository URL: the complete URL to your git repo. If you get an error here, make sure you can access it via the command line (you might need to accept the server’s fingerprint). In the “Advanced…” section, set the name of your repo to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;origin&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Branches to build: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Build triggers: Poll SCM every minute (type “* * * * *”).&lt;/li&gt;
  &lt;li&gt;Add build step: execute shell: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush test-run dcyclesite&lt;/code&gt;. If you are on Mac OS X, you might have to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[ $? = 0 ] || exit $?&lt;/code&gt; as explained &lt;a href=&quot;http://mediatribe.net/en/node/79&quot;&gt;here&lt;/a&gt;, otherwise your job will never fail.&lt;/li&gt;
  &lt;li&gt;Add post-build action “git publisher”. Push only if build succeeds to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch of your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;origin&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Add another action, “post-build task”, selecting “Run script only if all previous steps were successful”, and “Escalate script execution status to job status”. This is a script to actually deploy your site.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the last script, Jenkins will log into your remote site, pull the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch, and update your database. You might also want to backup your database here. In my case I have a separate job which periodically backs up my database. Here is some sample deployment code.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# set the site to maintenance mode
ssh me@example.com &quot;cd /path/to/drupal &amp;amp;&amp;amp; drush vset maintenance_mode 1 &amp;amp;&amp;amp;
# get the latest version of the code
git pull origin prod &amp;amp;&amp;amp;
# update the database
drush updb -y &amp;amp;&amp;amp;
# set maintenance mode to off
drush vset maintenance_mode 0 &amp;amp;&amp;amp;
# finally clear the cache
drush cc all&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that you can also use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rsync&lt;/code&gt; if you don’t want to have git on your production server. Whatever you use, the trick is for deployments to production to happen through Jenkins, not through a human.&lt;/p&gt;

&lt;p&gt;Now save your job and run it. It won’t work yet; don’t worry, this is normal, we haven’t finished yet.&lt;/p&gt;

&lt;h1 id=&quot;step-eight-make-your-jenkins-workspace-a-real-drupal-site&quot;&gt;Step Eight: make your Jenkins workspace a real Drupal site&lt;/h1&gt;

&lt;p&gt;To run tests, Jenkins will need a database, but we haven’t yet set one up. It will also need HTTP access to its workspace. Let’s do all this now.&lt;/p&gt;

&lt;p&gt;Return to configure your Jenkins job, and in “Advanced project options”, click “Advanced…”. Click “Use custom workspace” and set a path which will be available via an URL. For example, if your Jenkins server is on your Mac and you are using MAMP, you can set this to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Applications/MAMP/htdocs/mysite.jenkins&lt;/code&gt;. This workspace will be available, for example, via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:8888/mysite.jenkins/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Switch to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jenkins&lt;/code&gt; user and install a plain Drupal site with the Simpletest module:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo su jenkins
echo &apos;create database mysitejenkins&apos; | mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/mysitejenkins --account-name=root --account-pass=root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that you don’t need to deploy your site here using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush en example_deploy -y&lt;/code&gt;: all this site really is needed for is hosting tests. So we just need a plain Drupal site, with simpletest enabled:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en simpletest -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now set the base url of your workspace in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/default/settings.php&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$base_url = &apos;http://localhost:8888/mysite.jenkins&apos;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;step-nine-enjoy&quot;&gt;Step Nine: Enjoy!&lt;/h1&gt;

&lt;p&gt;That’s basically all there is to it! However, because of the sheer number of steps involved, it is probable that you will run into a problem and need to debug something or other. I will appreciate hearing from you, noting any pitfalls and comments you may have. With the above steps in place, you will be able to make any change to the codebase, adding tests and function, test locally, and push to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. Then sit back and look at your Jenkins dashboard and your production site. If all goes well, you will see your job kick off, and after a few minutes, if you refresh your production site, you will see it is in maintenance mode. Some time later, your Jenkins job will end in success and your production site, &lt;em&gt;with your new code&lt;/em&gt;, will be live again! Now bring your colleagues into the fold: this technique scales very well.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;Edit (2016-10-03): &lt;a href=&quot;http://blog.dcycle.com/blog/2016-10-02/when-not-to-use-drupal/&quot;&gt;This website is no longer Drupal-based&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Deployments are often one of the most pain-inducing aspects of the Drupal development cycle. I have talked to Drupal developers in several shops, and have found that best practices are often ignored in favor of cloning databases downstream, manually reproducing content on prod environments, following a series of error-prone manual steps on each environment, and other bad practices, all of which should be thrown out the door.&lt;/p&gt;

&lt;p&gt;In this article I am referring to deployment of site configuration (not content) on a Drupal 7 site. Configuration refers such aspects of your site as the default theme, CSS aggregation status, content types, views, vocabularies, and the like.&lt;/p&gt;

&lt;p&gt;Taking best practices to the extreme, it is possible to deploy &lt;em&gt;continually&lt;/em&gt;, dozens of times a day, automatically. The following procedure is a proof of concept, and you will probably want to adapt it to your needs, introducing a manual step perhaps, if only to make sure your deployments happen on fixed schedule.&lt;/p&gt;

&lt;p&gt;Still, I have started using the exact procedure discussed herein to deploy the website you are currently reading, &lt;a href=&quot;http://blog.dcycle.com&quot;&gt;blog.dcycle.com&lt;/a&gt;. Furthermore, &lt;a href=&quot;https://github.com/alberto56/dcyclesite&quot;&gt;the code is on Github&lt;/a&gt;, so anyone can reproduce the dcycle project site, without the content (I’ll detail how later on).&lt;/p&gt;

&lt;h1 id=&quot;initial-goal&quot;&gt;Initial goal&lt;/h1&gt;

&lt;p&gt;In an effort to demonstrate that the principles of the &lt;a href=&quot;http://blog.dcycle.com/&quot;&gt;Dcycle manifesto&lt;/a&gt; work well for a real — albeit simple — website, I have started to deploy changes to the Dcycle website itself via a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; with automatic testing, a continuous integration server (Jenkins), and continuous deployment. In fact, if you visit &lt;a href=&quot;http://blog.dcycle.com/&quot;&gt;the dcycle website&lt;/a&gt;, you might come across a maintenance page. This is a deployment in action, and chances are it’s happening automatically.&lt;/p&gt;

&lt;p&gt;So what is continuous deployment? For our purposes, it is a site development method which follows these principles:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The site’s code is under version control (we are using &lt;a href=&quot;http://git-scm.com&quot;&gt;Git&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;Our site is deployed, initially and incrementally, via a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Automatic testing confirms that the features we have developed actually work.&lt;/li&gt;
  &lt;li&gt;Two branches exist: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;, on which development occurs, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt;, considered stable, on which all tests have passed.&lt;/li&gt;
  &lt;li&gt;A continuous integration server (in our case &lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt;) monitors the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch, and moves code to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch only if automated tests pass.&lt;/li&gt;
  &lt;li&gt;The production site is never changed directly, but via a job in the continuous integration server.&lt;/li&gt;
  &lt;li&gt;Once the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch is updated, so is the production site.&lt;/li&gt;
  &lt;li&gt;Databases are never cloned, except to move legacy sites to your local development environment. (A legacy site, in the context of this article, is a site which you can’t deploy (minus the content) without cloning the database).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical workflow happens like this:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Code is committed and pushed to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch in the git repo.&lt;/li&gt;
  &lt;li&gt;Jenkins picks up on the change, runs the tests, and they fail. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch and the production site are untouched.&lt;/li&gt;
  &lt;li&gt;The problem leading to the failing test is fixed, and the code is pushed, again to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch in the git repo.&lt;/li&gt;
  &lt;li&gt;Jenkins picks up on the change, runs the tests, and this time they pass. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch is updated automatically, and the production site itself is updated. &lt;em&gt;Automatically&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;tools&quot;&gt;Tools&lt;/h1&gt;

&lt;p&gt;Before getting started, make sure you have the following.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A continuous integration server, which can be on your laptop if you wish. &lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt; is easy to set up, and took me five minutes to install on Mac OS, and another five minutes on CentOS. Just follow the instructions.&lt;/li&gt;
  &lt;li&gt;A central git repo. You can fork &lt;a href=&quot;https://github.com/alberto56/dcyclesite&quot;&gt;the code for the blog.dcycle.com website&lt;/a&gt; if you like.&lt;/li&gt;
  &lt;li&gt;A webserver on your laptop. I am using &lt;a href=&quot;http://www.mamp.info/en/index.html&quot;&gt;MAMP&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Access to your production website on the command line via SSH.&lt;/li&gt;
  &lt;li&gt;SSH public-private key access to the production server, to avoid being asked for passwords. This is important for Jenkins to modify the production server automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We won’t be using git hooks or Drupal’s GUI.&lt;/p&gt;

&lt;h1 id=&quot;step-one-pick-an-issue&quot;&gt;Step one: pick an issue&lt;/h1&gt;

&lt;p&gt;More often than not, we are working on &lt;em&gt;existing&lt;/em&gt; Drupal sites, not new ones, and we don’t have the luxury of redeveloping everything with best practices. So we’ll start with a single issue, either a bug or feature request. Here is a real-life example for the &lt;a href=&quot;http://blog.dcycle.com&quot;&gt;Dcycle website&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;I like the idea of each article having its ID reflected in the URL, as is the case with &lt;a href=&quot;http://stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt;. I want the path of my articles to be in the format &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/12345/title-of-the-post&lt;/code&gt;. I also want it to be possible to shorten the path and have it redirect the full path, so for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/12345&lt;/code&gt; redirects to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog/12345/title-of-the-post&lt;/code&gt;, as is the case on Stack Overflow.&lt;/p&gt;

&lt;p&gt;So, I started out with the goal of implementing this feature using continuous deployment and automated tests.&lt;/p&gt;

&lt;h1 id=&quot;step-two-create-a-local-version-of-the-website&quot;&gt;Step two: create a local version of the website&lt;/h1&gt;

&lt;p&gt;If your site has a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt; or something like it, download your code from git and deploy the site locally, using these commands, substituting your own site deployment module name and database credentials for those in the example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo &apos;create database example&apos; | mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/example --account-name=root --account-pass=root
drush en example_deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to try this at home and create a local version of the &lt;a href=&quot;http://blog.dcycle.com&quot;&gt;Dcycle website&lt;/a&gt;, make sure you have a webserver, PHP and MySQL installed, and run the following commands (if you want to actually modify the code, fork it first and use your project URL instead of mine). This example uses MAMP.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd /Applications/MAMP/htdocs
git clone https://github.com/alberto56/dcyclesite.git dcyclesample
cd dcyclesample
echo &apos;create database dcyclesample&apos; | mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/dcyclesample --account-name=root --account-pass=root
drush en dcycle_deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above will yield an empty website. Adding some generated content will make development easier:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en devel_generate -y
drush generate-content 50
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If there is no site deployment site, you can &lt;a href=&quot;http://blog.dcycle.com/blog/33&quot;&gt;clone the database&lt;/a&gt;, but don’t make a habit of it!&lt;/p&gt;

&lt;h1 id=&quot;step-three-make-sure-you-have-a-site-deployment-module&quot;&gt;Step three: make sure you have a site deployment module&lt;/h1&gt;

&lt;p&gt;To work well with continuous deployment, your site needs to have a consistent way of being initially and incrementally deployed. To achieve this, I recommend the use of a &lt;a href=&quot;http://blog.dcycle.com/blog/44&quot;&gt;site deployment module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Create one for your site (one already exists for the Dcycle website code, if you are using that), and make sure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file contains everything necessary to deploy your site. To make sure initial deployment and incremental deployment result in the same state, I just call all my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt; hooks from my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;install&lt;/code&gt; hook, and that has worked fine for me. Your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file might look something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @file
 * sites/default/modules/custom/dcycle_deploy/dcycle_deploy.install
 * Initial and incremental deployment of this website.
 */

/**
 * Implements hook_install().
 */
function dcycle_deploy_install() {
  for ($i = 7001; $i &amp;lt; 8000; $i++) {
    $candidate = &apos;dcycle_deploy_update_&apos; . $i;
    if (function_exists($candidate)) {
      $candidate();
    }
  }
}

/**
 * Admin menu
 */
function dcycle_deploy_update_7007() {
  module_enable(array(&apos;admin_menu_toolbar&apos;));
  module_disable(array(&apos;toolbar&apos;));
}

...

/**
 * Set dark_elegant as theme
 */
function dcycle_deploy_update_7015() {
  theme_enable(array(&apos;dark_elegant&apos;));
  variable_set(&apos;theme_default&apos;, &apos;dark_elegant&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above code sets the default theme and changes the toolbar to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin_menu_toolbar&lt;/code&gt;, which I prefer.&lt;/p&gt;

&lt;p&gt;Because these features were deployed at different times (the theme was changed after the toolbar was changed), &lt;a href=&quot;https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_update_N/7&quot;&gt;numbered update hooks&lt;/a&gt; are used.&lt;/p&gt;

&lt;p&gt;Notice how the install hook cycles through all the update hooks, ensuring that our initial deployment and incremental deployments result in the same state. For any given environment, now, regardless of the previous state, bringing it up to date is simply a matter of updating the database. The following script can now be used on any environment:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush vset maintenance_mode 1
drush updb -y
drush cc all
drush vset maintenance_mode 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above puts the site in maintenance mode, runs all the update hooks which have not yet been run, clears the caches and takes the site out of maintenance mode.&lt;/p&gt;

&lt;p&gt;We now have standardized deployment, both initial and incremental.&lt;/p&gt;

&lt;h1 id=&quot;step-four-write-a-failing-test&quot;&gt;Step four: write a failing test&lt;/h1&gt;

&lt;p&gt;For continuous deployment to be of any use, we need to have very high confidence in our tests. A good first step to that end is for our tests to actually exist. And a good way to ensure that your tests exist is to write them before anything else. This is &lt;a href=&quot;http://en.wikipedia.org/wiki/Test-driven_development&quot;&gt;Test-driven development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you cloned my git repo for this site, the “short path” feature, introduced above, has already been implemented and tested, so the test passes. Still, here is the code I had written, which initially was failing. You might want to write something similar, or add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test...()&lt;/code&gt; function to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.test&lt;/code&gt; file, for another feature.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * @file
 * sites/default/modules/custom/dcycle_deploy/dcycle_deploy.test
 * This file contains the testing code for this module
 */

// Test should run with this number of blog posts.
define(&apos;DCYCLE_DEPLOY_TEST_BLOG_COUNT&apos;, 5);

/**
 * The test case
 */
class dcyclesiteTestCase extends DrupalWebTestCase {
  /**
   * Info for this test case.
   */
  public static function getInfo() {
    return array(
      &apos;name&apos; =&amp;gt; t(&apos;dcyclesite: basic test&apos;),
      &apos;description&apos; =&amp;gt; t(&apos;describe test.&apos;),
      &apos;group&apos; =&amp;gt; &apos;dcyclesite&apos;,
    );
  }

  /*
   * Enable your module
   */
  public function setUp() {
    // set up a new site with default core modules, dcyclesite, and
    // dependencies.
    parent::setUp(&apos;dcycle_deploy&apos;);
  }

  /*
   * Test case for dcyclesite.
   */
  public function testModule() {
    $this-&amp;gt;loginAsRole(&apos;administrator&apos;);
    $blogs = array();
    for ($i = 1; $i &amp;lt;= DCYCLE_DEPLOY_TEST_BLOG_COUNT; $i++) {
      $this-&amp;gt;drupalCreateNode(array(&apos;type&apos; =&amp;gt; &apos;article&apos;, &apos;title&apos; =&amp;gt; &apos;É&apos; . $blogs[$i] = $this-&amp;gt;randomName()));
      foreach (array(&apos;blog&apos;, &apos;node&apos;) as $base) {
        // passing alias =&amp;gt; TRUE, otherwise, the test converts our call
        // to the alias before the query.
        $this-&amp;gt;drupalGet($base . &apos;/&apos; . $i, array(&apos;alias&apos; =&amp;gt; TRUE));
        // assertUrl() does not work here, because the current url (node/1)
        // equals node/1 and equals also its alias. We want it to equal its
        // alias only.
        $url = $this-&amp;gt;getUrl();
        global $base_url;
        $expected = $base_url . &apos;/blog/&apos; . $i . &apos;/e&apos; . strtolower($blogs[$i]);
        $this-&amp;gt;assertEqual($url, $expected , format_string(&apos;Blog can be accessed using @base/x and will redirect correctly because the end url (@url) is equal to @expected.&apos;, array(&apos;@base&apos; =&amp;gt; $base, &apos;@url&apos; =&amp;gt; $url, &apos;@expected&apos; =&amp;gt; $expected)));
      }
    }
  }

  /*
   * Login as administrator role.
   *
   * This can be a useful for tests in your deployment module, especially
   * if you create several roles in a Feature dependency.
   *
   * @param $role = &apos;administrator&apos;
   *   Log in as any role, or administrator by default.
   */
  public function loginAsRole($role = &apos;administrator&apos;) {
    // Get all of the roles in the system.
    $roles = user_roles();
    // Find the index for the role we want to assign to the user.
    $index = array_search($role, $roles);
    // Get the permissions for the role.
    $permissions = user_role_permissions(array(array_search($role, $roles) =&amp;gt; $role));
    // Create the user with the permissions.
    $user = $this-&amp;gt;drupalCreateUser(array_keys($permissions[$index]));
    // Assign the role.
    $user-&amp;gt;roles[$index] = $role;
    // Log in as this user
    if (!($user = user_save($user))) {
      throw new Exception(format_string(&apos;cannot save user with role @r&apos;, array(&apos;@r&apos; =&amp;gt; $role)));
    }
    $this-&amp;gt;drupalLogin($user);
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Don’t forget to reference your test in your .info file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
files[] = dcycle_deploy.test
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What are we doing in the automatic test, above?&lt;/p&gt;

&lt;p&gt;Take a look at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;setUp()&lt;/code&gt; function, which does everything required to create a new environment of this website. Because we have used a site deployment module, “everything” is simply a matter of enabling that module.&lt;/p&gt;

&lt;p&gt;The key here is that whether the scenario works or not on any given environment (local, prod, etc.) is irrelevant: it needs to work based on a &lt;em&gt;known good starting point&lt;/em&gt;. Databases are moving targets and it is thus irrelevant to test your code against an existing database (except if you want to monitor your production environment, which is a more advanced use case and outside the scope of this article). Therefore, we need to bring the throw-away testing database to a point where we can test run a test, and &lt;em&gt;know that our test will always yield the same result&lt;/em&gt;. The concept of a known good starting point is discussed in the book &lt;a href=&quot;http://www.amazon.com/Continuous-Delivery-Deployment-Automation-Addison-Wesley/dp/0321601912&quot;&gt;Continuous Delivery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Given a new throw-away environment, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testModule()&lt;/code&gt; now runs the test, defining a scenario which should work: we are logging in as an administrator, creating new blog posts (making sure to use foreign characters in the title), and then making sure the foreign characters are transliterated to ASCII characters and that our content redirects correctly when using only the ID. Let’s enable Simpletest now and make sure our test is visible and fails:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en simpletest -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now log into your site, and visit admin/config/development/testing, and run your test. If the desired functionality has not yet been developed, your test should fail.&lt;/p&gt;

&lt;h1 id=&quot;step-five-make-sure-the-test-passes&quot;&gt;Step five: make sure the test passes&lt;/h1&gt;

&lt;p&gt;At this point let’s switch gears and focus our energy on making our test pass. This normally involves several code iterations, and running the test dozens of times, until it passes.&lt;/p&gt;

&lt;p&gt;An important note for test-driven development: the initial test is an approximation, and may have to be modified during coding. The &lt;em&gt;spirit&lt;/em&gt; of the test, as opposed to the &lt;em&gt;letter&lt;/em&gt; of the test, should be conserved.&lt;/p&gt;

&lt;p&gt;Test-driven development has the interesting side effect that it makes it easier for teams to collaborate: if I am working with a team in a different time zone, it is less error-prone for me to instruct them to “make the test work on branch xyz and then merge it to master”, rather than explain everything I have in mind.&lt;/p&gt;

&lt;p&gt;In the case of the task at hand, I wrote some custom code in a new module, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dcyclesite&lt;/code&gt;, and then enabled some new modules and configuration. Don’t forget, all operations which modify the database have to be done in update hooks. Here is a partial example of how my site deployment module’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.install&lt;/code&gt; file looks after I made the test pass:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Enable some custom code
 */
function dcycle_deploy_update_7022() {
  # this is where my custom code is; check my github code if you
  # are curious.
  module_enable(array(&apos;dcyclesite&apos;));
}

/**
 * Pattern for articles
 */
function dcycle_deploy_update_7023() {
  variable_set(&apos;pathauto_node_article_pattern&apos;, &apos;blog/[node:nid]/[node:title]&apos;);
}

/**
 * Enable transliteration
 */
function dcycle_deploy_update_7024() {
  module_enable(array(&apos;transliteration&apos;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;step-six-make-the-test-work-in-the-command-line&quot;&gt;Step six: make the test work &lt;em&gt;in the command line&lt;/em&gt;&lt;/h1&gt;

&lt;p&gt;Coming back to continuous deployment, we need our tests to be run every time code is pushed to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch, and running our tests will eventually be done by our Jenkins server.&lt;/p&gt;

&lt;p&gt;The idea behind Jenkins is quite simple: run a script as a reaction to an event. The event is a change in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch. Jenkins, though, does not know how to fiddle around in a GUI. Therefore we must make it possible to run the tests in the command line. Fortunately this is quite easy:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush test-run dcyclesite
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above runs all tests in the test group “dcyclesite”. Change “dcyclesite” to whatever your group name is. For tests to run correctly from the command line, you must make sure you &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base_url&lt;/code&gt; is set correctly in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/default/settings.php&lt;/code&gt; file. This depends on your environment but must reflect the URL at which your site is accessible, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$base_url = &apos;http://localhost:8888/mywebsite&apos;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush test-run dcyclesite&lt;/code&gt; should fail if there is a failing test. I normally develop code using Simpletest in the GUI, and use the command-line for regression testing in Jenkins.&lt;/p&gt;

&lt;h1 id=&quot;step-seven-create-a-jenkins-job&quot;&gt;Step seven: create a Jenkins job&lt;/h1&gt;

&lt;p&gt;Now the fun starts: create a Jenkins job to continuously deploy. Simply put, we need our jenkins server to monitor the master branch of git, and if everything passes, move our code to the production branch, and, if we are feeling particularly confident in our tests, deploy to the production site.&lt;/p&gt;

&lt;p&gt;Jenkins is one of the easiest pieces of software I have ever installed. You can probably get a CI server up and running in a matter of minutes by downloading the appropriate package on the &lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once that is done, set up Jenkins:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jenkins&lt;/code&gt; user on your Jenkins server has an SSH key pair, and has public-private SSH key access to both the git repo and the production server. Note that this will allow anyone with access to your Jenkins server to access your production server and your git repo, so apply security best practices to your Jenkins server!&lt;/li&gt;
  &lt;li&gt;In Jenkins’s plugin manager page, install &lt;a href=&quot;https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin&quot;&gt;the Git plugin&lt;/a&gt; and the &lt;a href=&quot;http://wiki.hudson-ci.org/display/HUDSON/Post+build+task&quot;&gt;post-build task plugin&lt;/a&gt;, which allows you to add a second script if the first script succeeds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now create a single job with the following attributes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Source code management: git.&lt;/li&gt;
  &lt;li&gt;Source code repository URL: the complete URL to your git repo. If you get an error here, make sure you can access it via the command line (you might need to accept the server’s fingerprint). In the “Advanced…” section, set the name of your repo to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;origin&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Branches to build: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Build triggers: Poll SCM every minute (type “* * * * *”).&lt;/li&gt;
  &lt;li&gt;Add build step: execute shell: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush test-run dcyclesite&lt;/code&gt;. If you are on Mac OS X, you might have to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[ $? = 0 ] || exit $?&lt;/code&gt; as explained &lt;a href=&quot;http://mediatribe.net/en/node/79&quot;&gt;here&lt;/a&gt;, otherwise your job will never fail.&lt;/li&gt;
  &lt;li&gt;Add post-build action “git publisher”. Push only if build succeeds to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch of your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;origin&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Add another action, “post-build task”, selecting “Run script only if all previous steps were successful”, and “Escalate script execution status to job status”. This is a script to actually deploy your site.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the last script, Jenkins will log into your remote site, pull the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prod&lt;/code&gt; branch, and update your database. You might also want to backup your database here. In my case I have a separate job which periodically backs up my database. Here is some sample deployment code.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# set the site to maintenance mode
ssh me@example.com &quot;cd /path/to/drupal &amp;amp;&amp;amp; drush vset maintenance_mode 1 &amp;amp;&amp;amp;
# get the latest version of the code
git pull origin prod &amp;amp;&amp;amp;
# update the database
drush updb -y &amp;amp;&amp;amp;
# set maintenance mode to off
drush vset maintenance_mode 0 &amp;amp;&amp;amp;
# finally clear the cache
drush cc all&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that you can also use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rsync&lt;/code&gt; if you don’t want to have git on your production server. Whatever you use, the trick is for deployments to production to happen through Jenkins, not through a human.&lt;/p&gt;

&lt;p&gt;Now save your job and run it. It won’t work yet; don’t worry, this is normal, we haven’t finished yet.&lt;/p&gt;

&lt;h1 id=&quot;step-eight-make-your-jenkins-workspace-a-real-drupal-site&quot;&gt;Step Eight: make your Jenkins workspace a real Drupal site&lt;/h1&gt;

&lt;p&gt;To run tests, Jenkins will need a database, but we haven’t yet set one up. It will also need HTTP access to its workspace. Let’s do all this now.&lt;/p&gt;

&lt;p&gt;Return to configure your Jenkins job, and in “Advanced project options”, click “Advanced…”. Click “Use custom workspace” and set a path which will be available via an URL. For example, if your Jenkins server is on your Mac and you are using MAMP, you can set this to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Applications/MAMP/htdocs/mysite.jenkins&lt;/code&gt;. This workspace will be available, for example, via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:8888/mysite.jenkins/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Switch to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jenkins&lt;/code&gt; user and install a plain Drupal site with the Simpletest module:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo su jenkins
echo &apos;create database mysitejenkins&apos; | mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/mysitejenkins --account-name=root --account-pass=root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that you don’t need to deploy your site here using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush en example_deploy -y&lt;/code&gt;: all this site really is needed for is hosting tests. So we just need a plain Drupal site, with simpletest enabled:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush en simpletest -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now set the base url of your workspace in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/default/settings.php&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$base_url = &apos;http://localhost:8888/mysite.jenkins&apos;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;step-nine-enjoy&quot;&gt;Step Nine: Enjoy!&lt;/h1&gt;

&lt;p&gt;That’s basically all there is to it! However, because of the sheer number of steps involved, it is probable that you will run into a problem and need to debug something or other. I will appreciate hearing from you, noting any pitfalls and comments you may have. With the above steps in place, you will be able to make any change to the codebase, adding tests and function, test locally, and push to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt;. Then sit back and look at your Jenkins dashboard and your production site. If all goes well, you will see your job kick off, and after a few minutes, if you refresh your production site, you will see it is in maintenance mode. Some time later, your Jenkins job will end in success and your production site, &lt;em&gt;with your new code&lt;/em&gt;, will be live again! Now bring your colleagues into the fold: this technique scales very well.&lt;/p&gt;
</description>
        
        <pubDate>Fri, 13 Dec 2013 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/46/continuous-deployment-drupal-style/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/46/continuous-deployment-drupal-style/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>What is a site deployment module?</title>
        <description>&lt;p&gt;In a Drupal development-staging-production workflow, the best practice is for new features and bug fixes to be developed locally, then moved downstream to the staging environment, and later to production.&lt;/p&gt;

&lt;p&gt;Just how changes are pushed downstream varies, but typically the process includes &lt;a href=&quot;https://drupal.org/project/features&quot;&gt;Features&lt;/a&gt;, manual changes to the production user interface, &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;drush&lt;/a&gt; commands, and written procedures.&lt;/p&gt;

&lt;p&gt;Some examples include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A view which is part of a Feature called xyz_feature is modified; the feature is updated and pushed to the git repo; and then the feature is reverted using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush fr xyz_feature&lt;/code&gt; on the production site.&lt;/li&gt;
  &lt;li&gt;A new default theme is added to the development site and tested, and pushed to the git repo; and then the new theme is selected as default on the production site’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/appearance&lt;/code&gt; page.&lt;/li&gt;
  &lt;li&gt;Javascript aggregation is set on the dev site’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/config/development/performance&lt;/code&gt; page, and once everything works locally, it is set on the production via the user interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach is characterized by the following properties:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Each incremental deployment is different and must be documented as such.&lt;/li&gt;
  &lt;li&gt;If there exist several environments, one must keep track manually of what “remains to be done” on each environment.&lt;/li&gt;
  &lt;li&gt;The production database is regularly cloned downstream to a staging environment, but it is impossible to tell when was the last time it was cloned.&lt;/li&gt;
  &lt;li&gt;If an environment is out of date and does not contain any important data, it can be deleted and the staging environment can be re-cloned.&lt;/li&gt;
  &lt;li&gt;Many features (for example javascript aggregation) are never in version control, at best only documented in an out-of-date wiki, at worst in the memory of a long-gone developer.&lt;/li&gt;
  &lt;li&gt;New developers clone the staging database to create a local development environment.&lt;/li&gt;
  &lt;li&gt;Automated functional testing by a continuous integration server, if done at all, uses a clone of the staging database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main issue I have with this approach is that it overly relies on the database to store important configuration, and the database is not under version control. There is no way to tell who did what, and when.&lt;/p&gt;

&lt;h1 id=&quot;the-deployment-module&quot;&gt;The deployment module&lt;/h1&gt;

&lt;p&gt;Using a deployment module aims to meet the following goals:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Everything except content should be in version control: views, the default theme, settings like Javascript aggregation, etc.&lt;/li&gt;
  &lt;li&gt;Incremental deployments should always be performed following the same procedure.&lt;/li&gt;
  &lt;li&gt;Initial deployments (for example for a new developer or for a throwaway environment during an automated test) should be possible without cloning the database.&lt;/li&gt;
  &lt;li&gt;Tests should be run agains a known-good starting point, not a clone of a database.&lt;/li&gt;
  &lt;li&gt;New developers should be up and running without having to clone a database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, anything not in version control is unreliable, and cloning the database today can yield a bug which won’t be present if you clone the database tomorrow. So we’ll avoid cloning the database in most cases.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://blog.dcycle.com/manifesto&quot;&gt;Dcycle manifesto&lt;/a&gt; states that each site should have a deployment module whose job it is to keep track of deployment-related configuration. Once you have settled on a namespace for your project, for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example&lt;/code&gt;, by convention your deployment module should reside in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/custom/example_deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s now say that we are starting a project, and our first order of business is to create a specific view: we will create the view, export it as a feature, and make the feature a dependency of our deployment module. Starting now, if all your code is under version control, all new environments (production, continuous integration, testing, new local sites) are deployed the same way, simply by creating a database and enabling the deployment module. Using &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;Drush&lt;/a&gt;, you would call something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo &apos;create database example&apos; | mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/example --account-name=root --account-pass=root
drush en example_deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first line creates the database; the second line is the equivalent of clicking though Drupal’s installation procedure; and the third line activates the deployment module.&lt;/p&gt;

&lt;p&gt;Because you have set your feature to be a dependency of your deployment module, it is activated and your view is deployed.&lt;/p&gt;

&lt;h1 id=&quot;incremental-deployments&quot;&gt;Incremental deployments&lt;/h1&gt;

&lt;p&gt;We want the incremental deployment procedure to always be the same. Also, we don’t want it to involve cloning the database, because the database is in an unknown state (it is not under version control). Another reason we don’t want to clone the database is because we want to practice our incremental deployment procedure as must as possible, ideally several times a day, to catch any problems before we apply it to the production site.&lt;/p&gt;

&lt;p&gt;My incremental deployment procedure, for all my Drupal projects, uses &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;Drush&lt;/a&gt; and &lt;a href=&quot;https://drupal.org/project/registry_rebuild&quot;&gt;Registry rebuild&lt;/a&gt;, and goes as follows once the new code has been fetched via git:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush rr
drush vset maintenance_mode 1
drush updb -y
drush cc all
drush cron
drush vset maintenance_mode 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first line (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush rr&lt;/code&gt;) rebuild the registry in case we moved module files since the last deployment. A typical example is moving contrib modules from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/all/modules/&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/all/modules/contrib/&lt;/code&gt;: without rebuilding the registry, your site will be broken and all following commands will fail.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush vset maintenance_mode 1&lt;/code&gt; sets the site to maintenance mode during the update.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; runs all update hooks for contrib modules, core, and, importantly, your deployment module (we’ll get back to that in a second).&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cc all&lt;/code&gt; clears all caches, which can fix some problems during deployment.&lt;/p&gt;

&lt;p&gt;On some projects, I have found that running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cron&lt;/code&gt; at this point helps avoid hard-to-diagnose problems.&lt;/p&gt;

&lt;p&gt;Finally, move your site out of maintenance mode: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush vset maintenance_mode 0&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;hook_update_ns&quot;&gt;hook_update_N()s&lt;/h1&gt;

&lt;p&gt;Our goal is for all our deployments (features, bug fixes, new modules…) to be channelled though &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()s&lt;/code&gt;, so that the incremental deployment procedure introduced above will trigger them. Simply, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; are functions which are called only once for each environment.&lt;/p&gt;

&lt;p&gt;Each environment tracks the last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; called, and when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; is called, it checks the code for new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; and runs them if necessary. (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; is the equivalent of visiting the update.php page, but the latter method is unsupported by the Dcycle procedure, because it requires managing PHP timeouts, which we don’t want to do).&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()s&lt;/code&gt; is the same tried-and-true mechanism used to update database schemas for Drupal core and contrib modules, so we are not introducing anything new.&lt;/p&gt;

&lt;p&gt;Now let’s see how a few common tasks can be accomplished the Dcycle way:&lt;/p&gt;

&lt;h1 id=&quot;example-1-enabling-javascript-aggregation&quot;&gt;Example 1: enabling Javascript aggregation&lt;/h1&gt;

&lt;p&gt;Instead of fiddling with the production environment, leaving no trace of what you’ve done, here is an ideal workflow for enabling Javascript aggregation:&lt;/p&gt;

&lt;p&gt;First, in your issue tracker, &lt;em&gt;create an issue&lt;/em&gt; explaining why you want to enable aggregation, and take note of the issue number (for example #12345).&lt;/p&gt;

&lt;p&gt;Next, &lt;em&gt;figure out how to enable aggregation in code&lt;/em&gt;. In this case, a little reverse-engineering is required: on your local site, visit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/config/development/performance&lt;/code&gt; and inspect the “Aggregate JavaScript files” checkbox, noting its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; property: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preprocess_js&lt;/code&gt;. This is likely to be a variable. You can confirm that it works by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush vset preprocess_js 1&lt;/code&gt; and reloading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/config/development/performance&lt;/code&gt;. Call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush vset preprocess_js 0&lt;/code&gt; to turn it back off again. Many configuration pages work this way, but in some cases you’ll need to work a bit more in order to figure out how to affect a change programmatically, which has the neat side effect of providing you a better understanding of how Drupal works.&lt;/p&gt;

&lt;p&gt;Now, simply add the following code to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; in your deployment module’s .install file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * #12345: Enable javascript aggregation
 */
function example_deploy_update_7001() {
  variable_set(&apos;preprocess_js&apos;, 1);
  // you can also do this with Features and the Strongarm module.
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; on &lt;em&gt;any&lt;/em&gt; environment, including your local environment, should enable Javascript aggregation.&lt;/p&gt;

&lt;p&gt;It is important to realize that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()s&lt;/code&gt; are only called on environments where the deployment module is already in place, and not on new deployments. To make sure that new deployments and incremental deployments behave similarly, I call all my update hooks from my hook_install, as described &lt;a href=&quot;http://blog.dcycle.com/node/43&quot;&gt;in a previous post&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Implements hook_install().
 *
 * See http://blog.dcycle.com/node/43
 */
function example_deploy_install() {
  for ($i = 7001; $i &amp;lt; 8000; $i++) {
    $candidate = &apos;example_deploy_update_&apos; . $i;
    if (function_exists($candidate)) {
      $candidate();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you are satisfied with your work, commit it to version control:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add sites/all/modules/custom/example_deploy/example_deploy.install
git commit -am &apos;#12345 Enabled javascript aggregation&apos;
git push origin master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you can deploy this functionality to any other environment using the standard incremental deployment procedure, ideally after your continuous integration server has given you the green (or in the case of &lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt;, blue) light.&lt;/p&gt;

&lt;h1 id=&quot;example-2-changing-a-view&quot;&gt;Example 2: changing a view&lt;/h1&gt;

&lt;p&gt;If we already have a feature which is a dependency of our deployment module, we can modify our view; update our features using the Features interface at admin/structure/features or using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush fu xyz_feature -y&lt;/code&gt;; then adding a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; to our deployment module:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * #12346: Change view to remove html tags from trimmed body
 */
function example_deploy_update_7002() {
  features_revert(array(&apos;xyz_feature&apos; =&amp;gt; array(&apos;views_view&apos;)));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;views_view&lt;/code&gt; is the machine name of the Features component affecting views. If you want to revert other components, make sure you’re using the 2.x branch of Features, visit the page at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/structure/features/xyz_feature/recreate&lt;/code&gt; (where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xyz_feature&lt;/code&gt; is the machine name of your feature), and you’ll find the machine names of each component next to its human name (for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node&lt;/code&gt; for content types, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter&lt;/code&gt; for text formats, etc.).&lt;/p&gt;

&lt;h1 id=&quot;example-3-changing-the-default-theme&quot;&gt;Example 3: changing the default theme&lt;/h1&gt;

&lt;p&gt;Say we create a new default theme xyz and want to enable it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * #12347: New theme for the site
 */
function example_deploy_update_7003() {
  theme_enable(array(&apos;xyz&apos;));
  variable_set(&apos;theme_default&apos;, &apos;xyz&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;example-4-adding-and-removing-modules&quot;&gt;Example 4: adding and removing modules&lt;/h1&gt;

&lt;p&gt;I normally remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toolbar&lt;/code&gt; on all my sites and put &lt;a href=&quot;https://drupal.org/project/admin_menu&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin_menu&lt;/code&gt;&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin_menu_toolbar&lt;/code&gt; instead. To deploy the change, add admin_menu to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/contrib&lt;/code&gt; and add the following code to your deployment module:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * #12348: Add a drop-down menu instead of the default menu for admins
 */
function example_deploy_update_7004() {
  // make sure admin_menu has been downloaded and added to your git repo,
  // or this will fail.
  module_enable(array(&apos;admin_menu_toolbar&apos;));
  module_disable(array(&apos;toolbar&apos;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;dont-change-production-directly&quot;&gt;Don’t change production directly&lt;/h1&gt;

&lt;p&gt;Of course, nothing prevents clueless users from modifying views, modules and settings on the production site directly, so I like to add &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_requirements/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_requirements()&lt;/code&gt;&lt;/a&gt; to perform certain checks on each environment: for example, if Javascript aggregation is turned off, you might see a red line on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/reports/status&lt;/code&gt; saying “This site is designed to use Javascript aggregation, please turn it back on”. You might also check that all your Features are not overridden, that the right theme is on etc. If this technique is used correctly, when a bug is reported on the production site, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/reports/status&lt;/code&gt; page will let you know if any settings on the production site are not what you intended, and what your automated tests expect.&lt;/p&gt;

&lt;h1 id=&quot;next-steps-automated-testing-and-continuous-integration&quot;&gt;Next steps: automated testing and continuous integration&lt;/h1&gt;

&lt;p&gt;Now that everything we do is in version control, we no longer need to clone databases, except in some very limited circumstances. We can always fire up a new environment and add dummy content for development or testing; and, provided we’re using the same commit and the same operating system and version of PHP, etc., we’re sure to always get the same result (which is not the case with database cloning).&lt;/p&gt;

&lt;p&gt;Specifically, I normally add a .test file in my deployment module which enables the deployment module on a test environment, and runs tests to make sure things are working as expected.&lt;/p&gt;

&lt;p&gt;Once that is done, it becomes easy to create a Jenkins continuous integration job to monitor the master branch, and confirm that a new environment can be created and simpletests pass.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;In a Drupal development-staging-production workflow, the best practice is for new features and bug fixes to be developed locally, then moved downstream to the staging environment, and later to production.&lt;/p&gt;

&lt;p&gt;Just how changes are pushed downstream varies, but typically the process includes &lt;a href=&quot;https://drupal.org/project/features&quot;&gt;Features&lt;/a&gt;, manual changes to the production user interface, &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;drush&lt;/a&gt; commands, and written procedures.&lt;/p&gt;

&lt;p&gt;Some examples include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A view which is part of a Feature called xyz_feature is modified; the feature is updated and pushed to the git repo; and then the feature is reverted using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush fr xyz_feature&lt;/code&gt; on the production site.&lt;/li&gt;
  &lt;li&gt;A new default theme is added to the development site and tested, and pushed to the git repo; and then the new theme is selected as default on the production site’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/appearance&lt;/code&gt; page.&lt;/li&gt;
  &lt;li&gt;Javascript aggregation is set on the dev site’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/config/development/performance&lt;/code&gt; page, and once everything works locally, it is set on the production via the user interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach is characterized by the following properties:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Each incremental deployment is different and must be documented as such.&lt;/li&gt;
  &lt;li&gt;If there exist several environments, one must keep track manually of what “remains to be done” on each environment.&lt;/li&gt;
  &lt;li&gt;The production database is regularly cloned downstream to a staging environment, but it is impossible to tell when was the last time it was cloned.&lt;/li&gt;
  &lt;li&gt;If an environment is out of date and does not contain any important data, it can be deleted and the staging environment can be re-cloned.&lt;/li&gt;
  &lt;li&gt;Many features (for example javascript aggregation) are never in version control, at best only documented in an out-of-date wiki, at worst in the memory of a long-gone developer.&lt;/li&gt;
  &lt;li&gt;New developers clone the staging database to create a local development environment.&lt;/li&gt;
  &lt;li&gt;Automated functional testing by a continuous integration server, if done at all, uses a clone of the staging database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main issue I have with this approach is that it overly relies on the database to store important configuration, and the database is not under version control. There is no way to tell who did what, and when.&lt;/p&gt;

&lt;h1 id=&quot;the-deployment-module&quot;&gt;The deployment module&lt;/h1&gt;

&lt;p&gt;Using a deployment module aims to meet the following goals:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Everything except content should be in version control: views, the default theme, settings like Javascript aggregation, etc.&lt;/li&gt;
  &lt;li&gt;Incremental deployments should always be performed following the same procedure.&lt;/li&gt;
  &lt;li&gt;Initial deployments (for example for a new developer or for a throwaway environment during an automated test) should be possible without cloning the database.&lt;/li&gt;
  &lt;li&gt;Tests should be run agains a known-good starting point, not a clone of a database.&lt;/li&gt;
  &lt;li&gt;New developers should be up and running without having to clone a database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, anything not in version control is unreliable, and cloning the database today can yield a bug which won’t be present if you clone the database tomorrow. So we’ll avoid cloning the database in most cases.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;http://blog.dcycle.com/manifesto&quot;&gt;Dcycle manifesto&lt;/a&gt; states that each site should have a deployment module whose job it is to keep track of deployment-related configuration. Once you have settled on a namespace for your project, for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;example&lt;/code&gt;, by convention your deployment module should reside in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/custom/example_deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s now say that we are starting a project, and our first order of business is to create a specific view: we will create the view, export it as a feature, and make the feature a dependency of our deployment module. Starting now, if all your code is under version control, all new environments (production, continuous integration, testing, new local sites) are deployed the same way, simply by creating a database and enabling the deployment module. Using &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;Drush&lt;/a&gt;, you would call something like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo &apos;create database example&apos; | mysql -uroot -proot
drush si --db-url=mysql://root:root@localhost/example --account-name=root --account-pass=root
drush en example_deploy -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first line creates the database; the second line is the equivalent of clicking though Drupal’s installation procedure; and the third line activates the deployment module.&lt;/p&gt;

&lt;p&gt;Because you have set your feature to be a dependency of your deployment module, it is activated and your view is deployed.&lt;/p&gt;

&lt;h1 id=&quot;incremental-deployments&quot;&gt;Incremental deployments&lt;/h1&gt;

&lt;p&gt;We want the incremental deployment procedure to always be the same. Also, we don’t want it to involve cloning the database, because the database is in an unknown state (it is not under version control). Another reason we don’t want to clone the database is because we want to practice our incremental deployment procedure as must as possible, ideally several times a day, to catch any problems before we apply it to the production site.&lt;/p&gt;

&lt;p&gt;My incremental deployment procedure, for all my Drupal projects, uses &lt;a href=&quot;https://github.com/drush-ops/drush&quot;&gt;Drush&lt;/a&gt; and &lt;a href=&quot;https://drupal.org/project/registry_rebuild&quot;&gt;Registry rebuild&lt;/a&gt;, and goes as follows once the new code has been fetched via git:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush rr
drush vset maintenance_mode 1
drush updb -y
drush cc all
drush cron
drush vset maintenance_mode 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first line (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush rr&lt;/code&gt;) rebuild the registry in case we moved module files since the last deployment. A typical example is moving contrib modules from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/all/modules/&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/all/modules/contrib/&lt;/code&gt;: without rebuilding the registry, your site will be broken and all following commands will fail.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush vset maintenance_mode 1&lt;/code&gt; sets the site to maintenance mode during the update.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; runs all update hooks for contrib modules, core, and, importantly, your deployment module (we’ll get back to that in a second).&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cc all&lt;/code&gt; clears all caches, which can fix some problems during deployment.&lt;/p&gt;

&lt;p&gt;On some projects, I have found that running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush cron&lt;/code&gt; at this point helps avoid hard-to-diagnose problems.&lt;/p&gt;

&lt;p&gt;Finally, move your site out of maintenance mode: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush vset maintenance_mode 0&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;hook_update_ns&quot;&gt;hook_update_N()s&lt;/h1&gt;

&lt;p&gt;Our goal is for all our deployments (features, bug fixes, new modules…) to be channelled though &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()s&lt;/code&gt;, so that the incremental deployment procedure introduced above will trigger them. Simply, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; are functions which are called only once for each environment.&lt;/p&gt;

&lt;p&gt;Each environment tracks the last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; called, and when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; is called, it checks the code for new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; and runs them if necessary. (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; is the equivalent of visiting the update.php page, but the latter method is unsupported by the Dcycle procedure, because it requires managing PHP timeouts, which we don’t want to do).&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()s&lt;/code&gt; is the same tried-and-true mechanism used to update database schemas for Drupal core and contrib modules, so we are not introducing anything new.&lt;/p&gt;

&lt;p&gt;Now let’s see how a few common tasks can be accomplished the Dcycle way:&lt;/p&gt;

&lt;h1 id=&quot;example-1-enabling-javascript-aggregation&quot;&gt;Example 1: enabling Javascript aggregation&lt;/h1&gt;

&lt;p&gt;Instead of fiddling with the production environment, leaving no trace of what you’ve done, here is an ideal workflow for enabling Javascript aggregation:&lt;/p&gt;

&lt;p&gt;First, in your issue tracker, &lt;em&gt;create an issue&lt;/em&gt; explaining why you want to enable aggregation, and take note of the issue number (for example #12345).&lt;/p&gt;

&lt;p&gt;Next, &lt;em&gt;figure out how to enable aggregation in code&lt;/em&gt;. In this case, a little reverse-engineering is required: on your local site, visit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/config/development/performance&lt;/code&gt; and inspect the “Aggregate JavaScript files” checkbox, noting its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt; property: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preprocess_js&lt;/code&gt;. This is likely to be a variable. You can confirm that it works by calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush vset preprocess_js 1&lt;/code&gt; and reloading &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/config/development/performance&lt;/code&gt;. Call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush vset preprocess_js 0&lt;/code&gt; to turn it back off again. Many configuration pages work this way, but in some cases you’ll need to work a bit more in order to figure out how to affect a change programmatically, which has the neat side effect of providing you a better understanding of how Drupal works.&lt;/p&gt;

&lt;p&gt;Now, simply add the following code to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; in your deployment module’s .install file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * #12345: Enable javascript aggregation
 */
function example_deploy_update_7001() {
  variable_set(&apos;preprocess_js&apos;, 1);
  // you can also do this with Features and the Strongarm module.
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush updb -y&lt;/code&gt; on &lt;em&gt;any&lt;/em&gt; environment, including your local environment, should enable Javascript aggregation.&lt;/p&gt;

&lt;p&gt;It is important to realize that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()s&lt;/code&gt; are only called on environments where the deployment module is already in place, and not on new deployments. To make sure that new deployments and incremental deployments behave similarly, I call all my update hooks from my hook_install, as described &lt;a href=&quot;http://blog.dcycle.com/node/43&quot;&gt;in a previous post&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Implements hook_install().
 *
 * See http://blog.dcycle.com/node/43
 */
function example_deploy_install() {
  for ($i = 7001; $i &amp;lt; 8000; $i++) {
    $candidate = &apos;example_deploy_update_&apos; . $i;
    if (function_exists($candidate)) {
      $candidate();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you are satisfied with your work, commit it to version control:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git add sites/all/modules/custom/example_deploy/example_deploy.install
git commit -am &apos;#12345 Enabled javascript aggregation&apos;
git push origin master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you can deploy this functionality to any other environment using the standard incremental deployment procedure, ideally after your continuous integration server has given you the green (or in the case of &lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt;, blue) light.&lt;/p&gt;

&lt;h1 id=&quot;example-2-changing-a-view&quot;&gt;Example 2: changing a view&lt;/h1&gt;

&lt;p&gt;If we already have a feature which is a dependency of our deployment module, we can modify our view; update our features using the Features interface at admin/structure/features or using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drush fu xyz_feature -y&lt;/code&gt;; then adding a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_N()&lt;/code&gt; to our deployment module:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * #12346: Change view to remove html tags from trimmed body
 */
function example_deploy_update_7002() {
  features_revert(array(&apos;xyz_feature&apos; =&amp;gt; array(&apos;views_view&apos;)));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the above example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;views_view&lt;/code&gt; is the machine name of the Features component affecting views. If you want to revert other components, make sure you’re using the 2.x branch of Features, visit the page at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/structure/features/xyz_feature/recreate&lt;/code&gt; (where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xyz_feature&lt;/code&gt; is the machine name of your feature), and you’ll find the machine names of each component next to its human name (for example &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node&lt;/code&gt; for content types, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter&lt;/code&gt; for text formats, etc.).&lt;/p&gt;

&lt;h1 id=&quot;example-3-changing-the-default-theme&quot;&gt;Example 3: changing the default theme&lt;/h1&gt;

&lt;p&gt;Say we create a new default theme xyz and want to enable it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * #12347: New theme for the site
 */
function example_deploy_update_7003() {
  theme_enable(array(&apos;xyz&apos;));
  variable_set(&apos;theme_default&apos;, &apos;xyz&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;example-4-adding-and-removing-modules&quot;&gt;Example 4: adding and removing modules&lt;/h1&gt;

&lt;p&gt;I normally remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;toolbar&lt;/code&gt; on all my sites and put &lt;a href=&quot;https://drupal.org/project/admin_menu&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin_menu&lt;/code&gt;&lt;/a&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin_menu_toolbar&lt;/code&gt; instead. To deploy the change, add admin_menu to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites/*/modules/contrib&lt;/code&gt; and add the following code to your deployment module:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * #12348: Add a drop-down menu instead of the default menu for admins
 */
function example_deploy_update_7004() {
  // make sure admin_menu has been downloaded and added to your git repo,
  // or this will fail.
  module_enable(array(&apos;admin_menu_toolbar&apos;));
  module_disable(array(&apos;toolbar&apos;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;dont-change-production-directly&quot;&gt;Don’t change production directly&lt;/h1&gt;

&lt;p&gt;Of course, nothing prevents clueless users from modifying views, modules and settings on the production site directly, so I like to add &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_requirements/7&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_requirements()&lt;/code&gt;&lt;/a&gt; to perform certain checks on each environment: for example, if Javascript aggregation is turned off, you might see a red line on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/reports/status&lt;/code&gt; saying “This site is designed to use Javascript aggregation, please turn it back on”. You might also check that all your Features are not overridden, that the right theme is on etc. If this technique is used correctly, when a bug is reported on the production site, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin/reports/status&lt;/code&gt; page will let you know if any settings on the production site are not what you intended, and what your automated tests expect.&lt;/p&gt;

&lt;h1 id=&quot;next-steps-automated-testing-and-continuous-integration&quot;&gt;Next steps: automated testing and continuous integration&lt;/h1&gt;

&lt;p&gt;Now that everything we do is in version control, we no longer need to clone databases, except in some very limited circumstances. We can always fire up a new environment and add dummy content for development or testing; and, provided we’re using the same commit and the same operating system and version of PHP, etc., we’re sure to always get the same result (which is not the case with database cloning).&lt;/p&gt;

&lt;p&gt;Specifically, I normally add a .test file in my deployment module which enables the deployment module on a test environment, and runs tests to make sure things are working as expected.&lt;/p&gt;

&lt;p&gt;Once that is done, it becomes easy to create a Jenkins continuous integration job to monitor the master branch, and confirm that a new environment can be created and simpletests pass.&lt;/p&gt;
</description>
        
        <pubDate>Fri, 22 Nov 2013 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/44/what-site-deployment-module/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/44/what-site-deployment-module/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Case study: installing mock objects for testing Drupal-Nuxeo synchronisation</title>
        <description>&lt;p&gt;I recently inherited a Drupal project which periodically imported content from a &lt;a href=&quot;http://www.nuxeo.com/en&quot;&gt;Nuxeo&lt;/a&gt; server, synchronizing it with Drupal nodes, thus creating, updating and deleting nodes as need be. Nuxeo content was in no case modified by Drupal.&lt;/p&gt;

&lt;p&gt;The Nuxeo server was set up by a third-party provider with whom I had no contact.&lt;/p&gt;

&lt;p&gt;The site was not using mock objects or automated testing. A custom Drupal module was used, which leveraged the &lt;a href=&quot;https://drupal.org/project/cmis&quot;&gt;CMIS&lt;/a&gt; module.&lt;/p&gt;

&lt;p&gt;A series of problems were occurring with the setup, among them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Nuxeo Categories were supposed to map to Drupal taxonomy terms, but if a category was deleted from Nuxeo, the corresponding taxonomy term was not removed from the node in Drupal&lt;/li&gt;
  &lt;li&gt;If more than one category was added to a Nuxeo content, only the first was imported to Drupal&lt;/li&gt;
  &lt;li&gt;The site used what seemed like a &lt;a href=&quot;http://stackoverflow.com/questions/19684281&quot;&gt;custom implementation&lt;/a&gt; of the Nuxeo API, so it was hard to get help from the community. The custom implementation returns Nuxeo content IDs for some contents and Nuxeo revision IDs for others. After some testing, I did not manage to figure out in which circumstances content IDs or revision IDs were used.&lt;/li&gt;
  &lt;li&gt;The Nuxeo server’s clock was a few minutes late, and the custom module was comparing timestamps rather than revision numbers. As a result, if a Nuxeo content was modified less than five minutes after its previous modification, synchronisation did not occur correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These problems had a common symptom from the client’s perspective: “synchronisation does not happen correctly”. They also caused a common emotion: frustration.&lt;/p&gt;

&lt;p&gt;In order to implement a robust fix, trial and error was not enough. Here are the steps I followed to reproduce the problems, implement testing, and finally fix them.&lt;/p&gt;

&lt;h1 id=&quot;step-1-have-a-staging-environment-on-the-remote-system&quot;&gt;Step 1: Have a staging environment on the remote system&lt;/h1&gt;

&lt;p&gt;Making sure I had access to a “staging” nuxeo folder allowed me do do testing without messing up production data; I could also control the number of content items, making testing that much faster. This was a convenient stop-gap measure until I could set up mock objects and internal testing.&lt;/p&gt;

&lt;h1 id=&quot;step-2-have-a-local-dev-environment-of-the-site&quot;&gt;Step 2: Have a local dev environment of the site&lt;/h1&gt;

&lt;p&gt;This might go without saying, but of course you should have a local environment before attempting to modify Drupal. We use git for version control, and I just grabbed a copy of the production database to my local dev site. Because Drupal is only reading Nuxeo data, not modifying it, this is not too risky.&lt;/p&gt;

&lt;h1 id=&quot;step-3-create-a-level-of-abstraction-between-drupal-and-nuxeo&quot;&gt;Step 3: Create a level of abstraction between Drupal and Nuxeo&lt;/h1&gt;

&lt;p&gt;Before setting up a true mock object for interaction with Nuxeo, I first had to set up a level of abstraction, in effect a sort of switch, between Drupal and Nuxeo. Later on I would plug in a mock object.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://drupal.org/project/cmis&quot;&gt;CMIS&lt;/a&gt; module is not using automated testing, and does not seem to allow for any form of mocking or simulation, from what I can tell (a search of the strings ‘mock’, ‘simulate’, or ‘simulation’ came up empty for that project’s code).&lt;/p&gt;

&lt;p&gt;So now I had to decide between these two approaches:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;provide a patch for CMIS providing mock object functionality.&lt;/li&gt;
  &lt;li&gt;provide a level of abstraction between the custom code and CMIS itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because there is no real standard way (yet) to provide mock object functionality in Drupal modules, I have been using my own solution for a few projects: the &lt;a href=&quot;https://drupal.org/project/mockable&quot;&gt;Mockable&lt;/a&gt; module. Although I have released a beta version, the module is not widely used and I would rather wait for this or some other solution to be more accepted before submitting patches for third-party modules. I therefore decided to use Mockable between my own module and CMIS.&lt;/p&gt;

&lt;p&gt;The Mockable module is not meant to be active on production sites. Here is how I used it:&lt;/p&gt;

&lt;p&gt;First, I downloaded Mockable and activate the mockable module (but not the other modules in the Mockable project) on the local development site.&lt;/p&gt;

&lt;p&gt;Then, I identified the lines of code in my custom module which interacted with an external system (in this case the &lt;a href=&quot;https://drupal.org/project/cmis&quot;&gt;CMIS&lt;/a&gt; module). Here is one example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$object = cmisapi_getProperties(&apos;default&apos;,$document_id);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmisapi_getProperties()&lt;/code&gt; is defined in CMIS and I did not want to modify that module, so I am going to mock it instead.&lt;/p&gt;

&lt;p&gt;I started by installing &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;, and calling my custom code from the devel/php page with different sets of data on my Nuxeo staging environment. Adding a var dump or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpm()&lt;/code&gt; call helped me figure out the structure of the response from cmisapi_getProperties() in different circumstances:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$object = cmisapi_getProperties(&apos;default&apos;,$document_id);
// this should be removed after testing. dpm() is defined in the devel
// module.
dpm($object);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once I had a good idea of how this function works, I defined a new set of functions instead of calling cmisapi_getProperties() directly:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$object = cmis_ms_get_properties(&apos;default&apos;,$document_id);
...

/**
 * Mockable version of CMIS&apos;s cmisapi_getProperties().
 */
function cmis_ms_get_properties($type, $document_id) {
  if (module_exists(&apos;mockable&apos;)) {
    // if the Mockable function is active, as it might be on testing
    // and dev environments (not on prod), then call cmisapi_getProperties()
    // or cmisapi_getProperties_mock() (if it exists) depending on whether
    // mocking is turned on or not.
    $return = mockable(&apos;cmisapi_getProperties&apos;, $address, $document_id);
  }
  else {
    $return = cmisapi_getProperties($address, $document_id);
  }
  return $return;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;step-4-create-mock-objects-to-simulate-the-external-system&quot;&gt;Step 4: Create mock objects to simulate the external system&lt;/h1&gt;

&lt;p&gt;Now that my abstraction layer was in place, all I had to do was define some functions and objects to replace, in my developement and continuous environments, those used in production.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Mock version of CMIS&apos;s cmisapi_getProperties(), which will be called
 * instead of cmisapi_getProperties() if the Mockable version is installed
 * and mocking is turned on (using drush mockable-set).
 */
function cmis_ms_get_properties_mockable_mock($type, $document_id) {
  $return = new stdClass;
  ...

  // Do whatever you want here to best simulate all possible responses of
  // the real cmisapi_getProperties()

  return $return;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmisapi_getProperties()&lt;/code&gt; was not the only function which interacted with the third-party system. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new SoapClient($address, $options)&lt;/code&gt; and other such calls were scattered across my code. I had to figure out how each of these worked and
mock them appropriately.&lt;/p&gt;

&lt;p&gt;Your mock objects or mock functions can be as simple or complex as you need them to be. In my case I am using variables to switch between different simulated behaviours. For example, I can easily simulate a timeout or 500 error on my remote system.&lt;/p&gt;

&lt;h1 id=&quot;step-5-reproduce-a-problem&quot;&gt;Step 5: Reproduce a problem&lt;/h1&gt;

&lt;p&gt;Now that my mock function is in place, I can turn on mocking. With the Mockable module, this can be done with a GUI or with Drush:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush mockable-set
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Starting now, I need to understand and reproduce exactly, in my mock object, how each error occurs.&lt;/p&gt;

&lt;p&gt;In the case of taxonomy import problems, I can set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmis_ms_get_properties_mockable_mock()&lt;/code&gt; to always return two categories, and confirm that they don’t get imported into Drupal.&lt;/p&gt;

&lt;h1 id=&quot;step-6-write-a-failing-test&quot;&gt;Step 6: Write a failing test&lt;/h1&gt;

&lt;p&gt;Reproducing the problem manually with a mock object is a step in the right direction, but we need to make sure that once it’s fixed, it stays fixed. To do that I added the following .test file to my custom module (and linked to it in my .info file).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * The test case
 */
class mymoduleTestCase extends DrupalWebTestCase {
  /**
   * Info for this test case.
   */
  public static function getInfo() {
    return array(
      &apos;name&apos; =&amp;gt; t(&apos;mymodule: basic test&apos;),
      &apos;description&apos; =&amp;gt; t(&apos;describe test.&apos;),
      &apos;group&apos; =&amp;gt; &apos;mymodule&apos;,
    );
  }

  /*
   * Enable your module
   */
  public function setUp() {
    // set up a new site with default core modules, mymodule, and
    // dependencies.
    parent::setUp(&apos;mymodule&apos;, &apos;mockable&apos;);
  }

  /*
   * Test case for mymodule.
   */
  public function testModule() {
    // start using mock objects
    mockable_set();
  
    // sync with our mock version of Nuxeo, in which documents all have
    // two categories. Note that before adding this function to a test,
    // I had to modify it to use mockable functions and objects instead
    // of always interacting with external servers. Because we called
    // mockable_set(), above, if we have correctly defined mock objects,
    // the external server should not be hit at this point. A good way
    // of making sure is to deactivate internet access during the local
    // test.
    mymodule_sync_nuxeo();
  
    $node = node_load(1);
  
    $taxonomy_count = count($node-&amp;gt;field_tags[LANGUAGE_NONE]);
    $this-&amp;gt;assertTrue($taxonomy_count == 2, format_string(&apos;We were expecting 2 taxonomy terms and we have obtained @count&apos;, array(&apos;@count&apos; =&amp;gt; $taxonomy_count)));
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When I ran this test, I could confirm that the test failed because even though my mock object was defining two categories, only the first ended up as a taxonomy term on my node.&lt;/p&gt;

&lt;h1 id=&quot;step-7-fix-the-test&quot;&gt;Step 7: Fix the test&lt;/h1&gt;

&lt;p&gt;Now that I had a failing test, my job was to make sure the test passed. Now the trial and error phase can really being:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Try something in code (note: both your test and your logic are “code”).&lt;/li&gt;
  &lt;li&gt;Run the test.&lt;/li&gt;
  &lt;li&gt;If the test still fails, make sure your test’s logic makes sense and go back to step 1.&lt;/li&gt;
  &lt;li&gt;Do a manual test. If it fails, go back to step 1.&lt;/li&gt;
  &lt;li&gt;If the test passes, do a manual test, commit your code and push to master.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;continuous-integration&quot;&gt;Continuous integration&lt;/h1&gt;

&lt;p&gt;To avoid this test being broken by another change in the future, you can set up a Continous integration server (&lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt;, for example), and set it up so that it runs your test, and indeed all tests for your project, on each commit:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush test-run mymodule
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;conclusion-what-do-we-mean-by-fixed&quot;&gt;Conclusion: what do we mean by “fixed”?&lt;/h1&gt;

&lt;p&gt;Only once all of this is done, can we be confident to show our fix to the client, and mark it as fixed. A bug should be marked as fixed, or a new feature marked as done, when:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A test exists.&lt;/li&gt;
  &lt;li&gt;Mock objects are used to define external system behaviour.&lt;/li&gt;
  &lt;li&gt;The test passes.&lt;/li&gt;
  &lt;li&gt;Ideally, the test is checked with every new commit to avoid regressions.&lt;/li&gt;
&lt;/ul&gt;
</description>
        
          <description>&lt;p&gt;I recently inherited a Drupal project which periodically imported content from a &lt;a href=&quot;http://www.nuxeo.com/en&quot;&gt;Nuxeo&lt;/a&gt; server, synchronizing it with Drupal nodes, thus creating, updating and deleting nodes as need be. Nuxeo content was in no case modified by Drupal.&lt;/p&gt;

&lt;p&gt;The Nuxeo server was set up by a third-party provider with whom I had no contact.&lt;/p&gt;

&lt;p&gt;The site was not using mock objects or automated testing. A custom Drupal module was used, which leveraged the &lt;a href=&quot;https://drupal.org/project/cmis&quot;&gt;CMIS&lt;/a&gt; module.&lt;/p&gt;

&lt;p&gt;A series of problems were occurring with the setup, among them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Nuxeo Categories were supposed to map to Drupal taxonomy terms, but if a category was deleted from Nuxeo, the corresponding taxonomy term was not removed from the node in Drupal&lt;/li&gt;
  &lt;li&gt;If more than one category was added to a Nuxeo content, only the first was imported to Drupal&lt;/li&gt;
  &lt;li&gt;The site used what seemed like a &lt;a href=&quot;http://stackoverflow.com/questions/19684281&quot;&gt;custom implementation&lt;/a&gt; of the Nuxeo API, so it was hard to get help from the community. The custom implementation returns Nuxeo content IDs for some contents and Nuxeo revision IDs for others. After some testing, I did not manage to figure out in which circumstances content IDs or revision IDs were used.&lt;/li&gt;
  &lt;li&gt;The Nuxeo server’s clock was a few minutes late, and the custom module was comparing timestamps rather than revision numbers. As a result, if a Nuxeo content was modified less than five minutes after its previous modification, synchronisation did not occur correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These problems had a common symptom from the client’s perspective: “synchronisation does not happen correctly”. They also caused a common emotion: frustration.&lt;/p&gt;

&lt;p&gt;In order to implement a robust fix, trial and error was not enough. Here are the steps I followed to reproduce the problems, implement testing, and finally fix them.&lt;/p&gt;

&lt;h1 id=&quot;step-1-have-a-staging-environment-on-the-remote-system&quot;&gt;Step 1: Have a staging environment on the remote system&lt;/h1&gt;

&lt;p&gt;Making sure I had access to a “staging” nuxeo folder allowed me do do testing without messing up production data; I could also control the number of content items, making testing that much faster. This was a convenient stop-gap measure until I could set up mock objects and internal testing.&lt;/p&gt;

&lt;h1 id=&quot;step-2-have-a-local-dev-environment-of-the-site&quot;&gt;Step 2: Have a local dev environment of the site&lt;/h1&gt;

&lt;p&gt;This might go without saying, but of course you should have a local environment before attempting to modify Drupal. We use git for version control, and I just grabbed a copy of the production database to my local dev site. Because Drupal is only reading Nuxeo data, not modifying it, this is not too risky.&lt;/p&gt;

&lt;h1 id=&quot;step-3-create-a-level-of-abstraction-between-drupal-and-nuxeo&quot;&gt;Step 3: Create a level of abstraction between Drupal and Nuxeo&lt;/h1&gt;

&lt;p&gt;Before setting up a true mock object for interaction with Nuxeo, I first had to set up a level of abstraction, in effect a sort of switch, between Drupal and Nuxeo. Later on I would plug in a mock object.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://drupal.org/project/cmis&quot;&gt;CMIS&lt;/a&gt; module is not using automated testing, and does not seem to allow for any form of mocking or simulation, from what I can tell (a search of the strings ‘mock’, ‘simulate’, or ‘simulation’ came up empty for that project’s code).&lt;/p&gt;

&lt;p&gt;So now I had to decide between these two approaches:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;provide a patch for CMIS providing mock object functionality.&lt;/li&gt;
  &lt;li&gt;provide a level of abstraction between the custom code and CMIS itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because there is no real standard way (yet) to provide mock object functionality in Drupal modules, I have been using my own solution for a few projects: the &lt;a href=&quot;https://drupal.org/project/mockable&quot;&gt;Mockable&lt;/a&gt; module. Although I have released a beta version, the module is not widely used and I would rather wait for this or some other solution to be more accepted before submitting patches for third-party modules. I therefore decided to use Mockable between my own module and CMIS.&lt;/p&gt;

&lt;p&gt;The Mockable module is not meant to be active on production sites. Here is how I used it:&lt;/p&gt;

&lt;p&gt;First, I downloaded Mockable and activate the mockable module (but not the other modules in the Mockable project) on the local development site.&lt;/p&gt;

&lt;p&gt;Then, I identified the lines of code in my custom module which interacted with an external system (in this case the &lt;a href=&quot;https://drupal.org/project/cmis&quot;&gt;CMIS&lt;/a&gt; module). Here is one example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$object = cmisapi_getProperties(&apos;default&apos;,$document_id);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmisapi_getProperties()&lt;/code&gt; is defined in CMIS and I did not want to modify that module, so I am going to mock it instead.&lt;/p&gt;

&lt;p&gt;I started by installing &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;Devel&lt;/a&gt;, and calling my custom code from the devel/php page with different sets of data on my Nuxeo staging environment. Adding a var dump or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpm()&lt;/code&gt; call helped me figure out the structure of the response from cmisapi_getProperties() in different circumstances:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$object = cmisapi_getProperties(&apos;default&apos;,$document_id);
// this should be removed after testing. dpm() is defined in the devel
// module.
dpm($object);
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once I had a good idea of how this function works, I defined a new set of functions instead of calling cmisapi_getProperties() directly:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
$object = cmis_ms_get_properties(&apos;default&apos;,$document_id);
...

/**
 * Mockable version of CMIS&apos;s cmisapi_getProperties().
 */
function cmis_ms_get_properties($type, $document_id) {
  if (module_exists(&apos;mockable&apos;)) {
    // if the Mockable function is active, as it might be on testing
    // and dev environments (not on prod), then call cmisapi_getProperties()
    // or cmisapi_getProperties_mock() (if it exists) depending on whether
    // mocking is turned on or not.
    $return = mockable(&apos;cmisapi_getProperties&apos;, $address, $document_id);
  }
  else {
    $return = cmisapi_getProperties($address, $document_id);
  }
  return $return;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;step-4-create-mock-objects-to-simulate-the-external-system&quot;&gt;Step 4: Create mock objects to simulate the external system&lt;/h1&gt;

&lt;p&gt;Now that my abstraction layer was in place, all I had to do was define some functions and objects to replace, in my developement and continuous environments, those used in production.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Mock version of CMIS&apos;s cmisapi_getProperties(), which will be called
 * instead of cmisapi_getProperties() if the Mockable version is installed
 * and mocking is turned on (using drush mockable-set).
 */
function cmis_ms_get_properties_mockable_mock($type, $document_id) {
  $return = new stdClass;
  ...

  // Do whatever you want here to best simulate all possible responses of
  // the real cmisapi_getProperties()

  return $return;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmisapi_getProperties()&lt;/code&gt; was not the only function which interacted with the third-party system. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new SoapClient($address, $options)&lt;/code&gt; and other such calls were scattered across my code. I had to figure out how each of these worked and
mock them appropriately.&lt;/p&gt;

&lt;p&gt;Your mock objects or mock functions can be as simple or complex as you need them to be. In my case I am using variables to switch between different simulated behaviours. For example, I can easily simulate a timeout or 500 error on my remote system.&lt;/p&gt;

&lt;h1 id=&quot;step-5-reproduce-a-problem&quot;&gt;Step 5: Reproduce a problem&lt;/h1&gt;

&lt;p&gt;Now that my mock function is in place, I can turn on mocking. With the Mockable module, this can be done with a GUI or with Drush:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush mockable-set
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Starting now, I need to understand and reproduce exactly, in my mock object, how each error occurs.&lt;/p&gt;

&lt;p&gt;In the case of taxonomy import problems, I can set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cmis_ms_get_properties_mockable_mock()&lt;/code&gt; to always return two categories, and confirm that they don’t get imported into Drupal.&lt;/p&gt;

&lt;h1 id=&quot;step-6-write-a-failing-test&quot;&gt;Step 6: Write a failing test&lt;/h1&gt;

&lt;p&gt;Reproducing the problem manually with a mock object is a step in the right direction, but we need to make sure that once it’s fixed, it stays fixed. To do that I added the following .test file to my custom module (and linked to it in my .info file).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * The test case
 */
class mymoduleTestCase extends DrupalWebTestCase {
  /**
   * Info for this test case.
   */
  public static function getInfo() {
    return array(
      &apos;name&apos; =&amp;gt; t(&apos;mymodule: basic test&apos;),
      &apos;description&apos; =&amp;gt; t(&apos;describe test.&apos;),
      &apos;group&apos; =&amp;gt; &apos;mymodule&apos;,
    );
  }

  /*
   * Enable your module
   */
  public function setUp() {
    // set up a new site with default core modules, mymodule, and
    // dependencies.
    parent::setUp(&apos;mymodule&apos;, &apos;mockable&apos;);
  }

  /*
   * Test case for mymodule.
   */
  public function testModule() {
    // start using mock objects
    mockable_set();
  
    // sync with our mock version of Nuxeo, in which documents all have
    // two categories. Note that before adding this function to a test,
    // I had to modify it to use mockable functions and objects instead
    // of always interacting with external servers. Because we called
    // mockable_set(), above, if we have correctly defined mock objects,
    // the external server should not be hit at this point. A good way
    // of making sure is to deactivate internet access during the local
    // test.
    mymodule_sync_nuxeo();
  
    $node = node_load(1);
  
    $taxonomy_count = count($node-&amp;gt;field_tags[LANGUAGE_NONE]);
    $this-&amp;gt;assertTrue($taxonomy_count == 2, format_string(&apos;We were expecting 2 taxonomy terms and we have obtained @count&apos;, array(&apos;@count&apos; =&amp;gt; $taxonomy_count)));
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When I ran this test, I could confirm that the test failed because even though my mock object was defining two categories, only the first ended up as a taxonomy term on my node.&lt;/p&gt;

&lt;h1 id=&quot;step-7-fix-the-test&quot;&gt;Step 7: Fix the test&lt;/h1&gt;

&lt;p&gt;Now that I had a failing test, my job was to make sure the test passed. Now the trial and error phase can really being:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Try something in code (note: both your test and your logic are “code”).&lt;/li&gt;
  &lt;li&gt;Run the test.&lt;/li&gt;
  &lt;li&gt;If the test still fails, make sure your test’s logic makes sense and go back to step 1.&lt;/li&gt;
  &lt;li&gt;Do a manual test. If it fails, go back to step 1.&lt;/li&gt;
  &lt;li&gt;If the test passes, do a manual test, commit your code and push to master.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;continuous-integration&quot;&gt;Continuous integration&lt;/h1&gt;

&lt;p&gt;To avoid this test being broken by another change in the future, you can set up a Continous integration server (&lt;a href=&quot;http://jenkins-ci.org&quot;&gt;Jenkins&lt;/a&gt;, for example), and set it up so that it runs your test, and indeed all tests for your project, on each commit:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush test-run mymodule
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;conclusion-what-do-we-mean-by-fixed&quot;&gt;Conclusion: what do we mean by “fixed”?&lt;/h1&gt;

&lt;p&gt;Only once all of this is done, can we be confident to show our fix to the client, and mark it as fixed. A bug should be marked as fixed, or a new feature marked as done, when:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A test exists.&lt;/li&gt;
  &lt;li&gt;Mock objects are used to define external system behaviour.&lt;/li&gt;
  &lt;li&gt;The test passes.&lt;/li&gt;
  &lt;li&gt;Ideally, the test is checked with every new commit to avoid regressions.&lt;/li&gt;
&lt;/ul&gt;
</description>
        
        <pubDate>Wed, 13 Nov 2013 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/38/case-study-installing-mock-objects-testing-drupal-nuxeo-synchronisation/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/38/case-study-installing-mock-objects-testing-drupal-nuxeo-synchronisation/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>An approach to deploying translations for a multilingual site</title>
        <description>&lt;p&gt;Let’s say you are working locally and you need to add a new module to the site. Here is an example with &lt;a href=&quot;https://drupal.org/project/logintoboggan&quot;&gt;Login Toboggan&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;Let’s start by downloading the module to our local dev site&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush dl logintoboggan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead of enabling it outright, we’ll want to add it as a dependency to our deploy module, and define a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_n()&lt;/code&gt; to enable it on existing environments.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;; in your deploy module&apos;s .info file
dependencies[] = logintoboggan

/**
 * Enable logintoboggan in our deploy module&apos;s .install file
 */
function mysite_deploy_update_7001() {
  module_enable(array(&apos;logintoboggan&apos;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Deployable module settings need to be in code as well, so we can have the same configuration on all environments (local, development, stage, production). Here is an example with Login Toboggan’s user login form on Access Denied:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Set logintoboggan to put login form on 403 pages
 */
function mysite_deploy_update_7002() {
  variable_set(&apos;site_403&apos;, &apos;toboggan/denied&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Make sure the above is also called during initial deployment, in your deployment module’s .install file (don’t forget: your continuous integration server and new members of the team will need to deploy new environments for your site):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Implements hook_install().
 */
function mysite_deploy_install() {
  mysite_deploy_update_7002();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, you can enjoy this new functionality either on a new site environment by enabling mysite_deploy, or an an existing environment by updating your database:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush updb -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, if you are running a multilingual site, the newly-added Login Toboggan module will not be translated. I avoid fetching the translations from localize.drupal.org in the update hook, because you can’t be sure it will work on all environments (for example, some clients disable internet access on their web servers for security reasons).&lt;/p&gt;

&lt;p&gt;Rather, I prefer to download the translations myself and import them from the local repo in the update hook. Here’s how:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Start by visiting http://localize.drupal.org and finding your project&lt;/li&gt;
  &lt;li&gt;In the export tab, I like to check the Add Suggestions box, so, in the case of strings without “official” translations, at least I have something.&lt;/li&gt;
  &lt;li&gt;Click Export Gettext file, which will save a .po or .po.txt file to your computer, for example, for Login Toboggan in French, the file is logintoboggan-all.fr.po.txt&lt;/li&gt;
  &lt;li&gt;I put this file in my deployment module, for example sites/all/modules/custom/mysite_deploy/translations/ (if you have a .txt extension you can remove it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, import in an install hook&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Import translations for Login Toboggan.
 */
function mysite_deploy_update_7003() {
  _mysite_deploy_import_po(&apos;logintoboggan-all.fr.po&apos;);
}

/**
 * Import translation po file
 *
 * @param $filename
 *   A filename in mysite_deploy/translations/
 */
function _mysite_deploy_import_po($file) {
  try {
    // Figure out the full filepath
    $filepath = DRUPAL_ROOT . &apos;/&apos; . drupal_get_path(&apos;module&apos;, &apos;mysite_deploy&apos;) . &apos;/translations/&apos; . $file;
    // move the contents of the file to the public stream. I can&apos;t get
    // _locale_import_po() to work with the file directly.
    $contents = file_get_contents($filepath);
    // In some cases the destination file might already exist, so I&apos;ll create a
    // random name; there probably is a better way of doing this...
    $random = md5($filepath) . rand(1000000000, 9999999999);
    $file = file_save_data($contents, &apos;public://po-import&apos; . $random . &apos;.po&apos;);
    // finally import the file from the public stream.
    _locale_import_po($file, &apos;fr&apos;, LOCALE_IMPORT_OVERWRITE, &apos;default&apos;);
  }
  catch (Exception $e) {
    // don&apos;t break the update process for translations.
    drupal_set_message(t(&apos;Oops, could not import the translation @f. Other updates will still take place (@r).&apos;, array(&apos;@f&apos; =&amp;gt; $filename, &apos;@r&apos; =&amp;gt; $e-&amp;gt;getMessage())));
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Don’t forget to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysite_deploy_update_7003()&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysite_install()&lt;/code&gt;, to make sure that the translations are available to new deployments as well.&lt;/p&gt;

&lt;p&gt;There is room for improvement in the above code, I admit, but I like this approach because it’s testable and does not depend on internet access during updates. Also, the same approach can be used to translate your custom modules.&lt;/p&gt;

&lt;p&gt;Update: thanks to mikran for implementing &lt;a href=&quot;https://www.drupal.org/node/2371049&quot;&gt;a module&lt;/a&gt; (for now in sandbox mode) which expands on this idea.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;Let’s say you are working locally and you need to add a new module to the site. Here is an example with &lt;a href=&quot;https://drupal.org/project/logintoboggan&quot;&gt;Login Toboggan&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;Let’s start by downloading the module to our local dev site&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush dl logintoboggan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead of enabling it outright, we’ll want to add it as a dependency to our deploy module, and define a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hook_update_n()&lt;/code&gt; to enable it on existing environments.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;; in your deploy module&apos;s .info file
dependencies[] = logintoboggan

/**
 * Enable logintoboggan in our deploy module&apos;s .install file
 */
function mysite_deploy_update_7001() {
  module_enable(array(&apos;logintoboggan&apos;));
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Deployable module settings need to be in code as well, so we can have the same configuration on all environments (local, development, stage, production). Here is an example with Login Toboggan’s user login form on Access Denied:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Set logintoboggan to put login form on 403 pages
 */
function mysite_deploy_update_7002() {
  variable_set(&apos;site_403&apos;, &apos;toboggan/denied&apos;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Make sure the above is also called during initial deployment, in your deployment module’s .install file (don’t forget: your continuous integration server and new members of the team will need to deploy new environments for your site):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Implements hook_install().
 */
function mysite_deploy_install() {
  mysite_deploy_update_7002();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, you can enjoy this new functionality either on a new site environment by enabling mysite_deploy, or an an existing environment by updating your database:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;drush updb -y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However, if you are running a multilingual site, the newly-added Login Toboggan module will not be translated. I avoid fetching the translations from localize.drupal.org in the update hook, because you can’t be sure it will work on all environments (for example, some clients disable internet access on their web servers for security reasons).&lt;/p&gt;

&lt;p&gt;Rather, I prefer to download the translations myself and import them from the local repo in the update hook. Here’s how:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Start by visiting http://localize.drupal.org and finding your project&lt;/li&gt;
  &lt;li&gt;In the export tab, I like to check the Add Suggestions box, so, in the case of strings without “official” translations, at least I have something.&lt;/li&gt;
  &lt;li&gt;Click Export Gettext file, which will save a .po or .po.txt file to your computer, for example, for Login Toboggan in French, the file is logintoboggan-all.fr.po.txt&lt;/li&gt;
  &lt;li&gt;I put this file in my deployment module, for example sites/all/modules/custom/mysite_deploy/translations/ (if you have a .txt extension you can remove it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, import in an install hook&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Import translations for Login Toboggan.
 */
function mysite_deploy_update_7003() {
  _mysite_deploy_import_po(&apos;logintoboggan-all.fr.po&apos;);
}

/**
 * Import translation po file
 *
 * @param $filename
 *   A filename in mysite_deploy/translations/
 */
function _mysite_deploy_import_po($file) {
  try {
    // Figure out the full filepath
    $filepath = DRUPAL_ROOT . &apos;/&apos; . drupal_get_path(&apos;module&apos;, &apos;mysite_deploy&apos;) . &apos;/translations/&apos; . $file;
    // move the contents of the file to the public stream. I can&apos;t get
    // _locale_import_po() to work with the file directly.
    $contents = file_get_contents($filepath);
    // In some cases the destination file might already exist, so I&apos;ll create a
    // random name; there probably is a better way of doing this...
    $random = md5($filepath) . rand(1000000000, 9999999999);
    $file = file_save_data($contents, &apos;public://po-import&apos; . $random . &apos;.po&apos;);
    // finally import the file from the public stream.
    _locale_import_po($file, &apos;fr&apos;, LOCALE_IMPORT_OVERWRITE, &apos;default&apos;);
  }
  catch (Exception $e) {
    // don&apos;t break the update process for translations.
    drupal_set_message(t(&apos;Oops, could not import the translation @f. Other updates will still take place (@r).&apos;, array(&apos;@f&apos; =&amp;gt; $filename, &apos;@r&apos; =&amp;gt; $e-&amp;gt;getMessage())));
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Don’t forget to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysite_deploy_update_7003()&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mysite_install()&lt;/code&gt;, to make sure that the translations are available to new deployments as well.&lt;/p&gt;

&lt;p&gt;There is room for improvement in the above code, I admit, but I like this approach because it’s testable and does not depend on internet access during updates. Also, the same approach can be used to translate your custom modules.&lt;/p&gt;

&lt;p&gt;Update: thanks to mikran for implementing &lt;a href=&quot;https://www.drupal.org/node/2371049&quot;&gt;a module&lt;/a&gt; (for now in sandbox mode) which expands on this idea.&lt;/p&gt;
</description>
        
        <pubDate>Wed, 13 Nov 2013 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/28/approach-deploying-translations-multilingual-site/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/28/approach-deploying-translations-multilingual-site/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
      
      <item>
        <title>Don&apos;t perform logic in your hook_form_submit: use an API</title>
        <description>&lt;p&gt;Examples like these are rampant throughout Drupal 7, in &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21block%21block.admin.inc/function/block_admin_display_form_submit/6&quot;&gt;block_admin_display_form_submit()&lt;/a&gt;, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Form submission handler for block_admin_display_form().
 *
 * @see block_admin_display_form()
 */
function block_admin_display_form_submit($form, &amp;amp;$form_state) {
  $transaction = db_transaction();
  try {
    foreach ($form_state[&apos;values&apos;][&apos;blocks&apos;] as $block) {
      $block[&apos;status&apos;] = (int) ($block[&apos;region&apos;] != BLOCK_REGION_NONE);
      $block[&apos;region&apos;] = $block[&apos;status&apos;] ? $block[&apos;region&apos;] : &apos;&apos;;
      db_update(&apos;block&apos;)
        -&amp;gt;fields(array(
          &apos;status&apos; =&amp;gt; $block[&apos;status&apos;],
          &apos;weight&apos; =&amp;gt; $block[&apos;weight&apos;],
          &apos;region&apos; =&amp;gt; $block[&apos;region&apos;],
        ))
        -&amp;gt;condition(&apos;module&apos;, $block[&apos;module&apos;])
        -&amp;gt;condition(&apos;delta&apos;, $block[&apos;delta&apos;])
        -&amp;gt;condition(&apos;theme&apos;, $block[&apos;theme&apos;])
        -&amp;gt;execute();
    }
  }
  catch (Exception $e) {
    $transaction-&amp;gt;rollback();
    watchdog_exception(&apos;block&apos;, $e);
    throw $e;
  }
  drupal_set_message(t(&apos;The block settings have been updated.&apos;));
  cache_clear_all();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What’s wrong with this is that the &lt;em&gt;logic&lt;/em&gt; for performing the desired action (moving a block to a different region) is tied to the use of a form, in the GUI. Let’s say you want a third-party module to move the navigation and powered-by block out of the theme xyz, one would expect there to exist, in the API, a function resembling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_move($blocks, $region, $theme)&lt;/code&gt;, but there is no such function.&lt;/p&gt;

&lt;p&gt;On a recent project where I needed to do exactly that, I installed and enabled &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;devel&lt;/a&gt;, and put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpm($form); dpm($form_state);&lt;/code&gt; at the top of the block_admin_display_form_submit() function then went through the actions in the GUI, &lt;em&gt;to figure out what Drupal is doing&lt;/em&gt;, finally coming up with this code.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;foreach (array(&apos;navigation&apos;, &apos;powered-by&apos;) as $block) {
  $num_updated = db_update(&apos;block&apos;) // Table name no longer needs {}
    -&amp;gt;fields(array(
      &apos;region&apos; =&amp;gt; &apos;-1&apos;,
    ))
    -&amp;gt;condition(&apos;delta&apos;, $block, &apos;=&apos;)
    -&amp;gt;condition(&apos;theme&apos;, &apos;xyz&apos;, &apos;=&apos;)
    -&amp;gt;execute();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s &lt;a href=&quot;http://en.wikipedia.org/wiki/Reverse_engineering&quot;&gt;reverse engineering&lt;/a&gt;, and it’s time-consuming, error-prone, and developer-unfriendly.&lt;/p&gt;

&lt;p&gt;Recently in one of my own modules I realized I had &lt;a href=&quot;https://drupal.org/node/2100531&quot;&gt;made the same mistake&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The correct approach is to define an api (for example a function like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_move()&lt;/code&gt; in the above example, and call that API function from your form- and GUI-related functions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_admin_display_form_submit()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The result will be that developers will have as easy a time interacting with your module as human users. This will open the door to third-party interaction, adding value to your module.&lt;/p&gt;

&lt;p&gt;Also, it will allow you to run more automated tests without actually loading pages, which is faster.&lt;/p&gt;
</description>
        
          <description>&lt;p&gt;Examples like these are rampant throughout Drupal 7, in &lt;a href=&quot;https://api.drupal.org/api/drupal/modules%21block%21block.admin.inc/function/block_admin_display_form_submit/6&quot;&gt;block_admin_display_form_submit()&lt;/a&gt;, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/**
 * Form submission handler for block_admin_display_form().
 *
 * @see block_admin_display_form()
 */
function block_admin_display_form_submit($form, &amp;amp;$form_state) {
  $transaction = db_transaction();
  try {
    foreach ($form_state[&apos;values&apos;][&apos;blocks&apos;] as $block) {
      $block[&apos;status&apos;] = (int) ($block[&apos;region&apos;] != BLOCK_REGION_NONE);
      $block[&apos;region&apos;] = $block[&apos;status&apos;] ? $block[&apos;region&apos;] : &apos;&apos;;
      db_update(&apos;block&apos;)
        -&amp;gt;fields(array(
          &apos;status&apos; =&amp;gt; $block[&apos;status&apos;],
          &apos;weight&apos; =&amp;gt; $block[&apos;weight&apos;],
          &apos;region&apos; =&amp;gt; $block[&apos;region&apos;],
        ))
        -&amp;gt;condition(&apos;module&apos;, $block[&apos;module&apos;])
        -&amp;gt;condition(&apos;delta&apos;, $block[&apos;delta&apos;])
        -&amp;gt;condition(&apos;theme&apos;, $block[&apos;theme&apos;])
        -&amp;gt;execute();
    }
  }
  catch (Exception $e) {
    $transaction-&amp;gt;rollback();
    watchdog_exception(&apos;block&apos;, $e);
    throw $e;
  }
  drupal_set_message(t(&apos;The block settings have been updated.&apos;));
  cache_clear_all();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What’s wrong with this is that the &lt;em&gt;logic&lt;/em&gt; for performing the desired action (moving a block to a different region) is tied to the use of a form, in the GUI. Let’s say you want a third-party module to move the navigation and powered-by block out of the theme xyz, one would expect there to exist, in the API, a function resembling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_move($blocks, $region, $theme)&lt;/code&gt;, but there is no such function.&lt;/p&gt;

&lt;p&gt;On a recent project where I needed to do exactly that, I installed and enabled &lt;a href=&quot;https://drupal.org/project/devel&quot;&gt;devel&lt;/a&gt;, and put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dpm($form); dpm($form_state);&lt;/code&gt; at the top of the block_admin_display_form_submit() function then went through the actions in the GUI, &lt;em&gt;to figure out what Drupal is doing&lt;/em&gt;, finally coming up with this code.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;foreach (array(&apos;navigation&apos;, &apos;powered-by&apos;) as $block) {
  $num_updated = db_update(&apos;block&apos;) // Table name no longer needs {}
    -&amp;gt;fields(array(
      &apos;region&apos; =&amp;gt; &apos;-1&apos;,
    ))
    -&amp;gt;condition(&apos;delta&apos;, $block, &apos;=&apos;)
    -&amp;gt;condition(&apos;theme&apos;, &apos;xyz&apos;, &apos;=&apos;)
    -&amp;gt;execute();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s &lt;a href=&quot;http://en.wikipedia.org/wiki/Reverse_engineering&quot;&gt;reverse engineering&lt;/a&gt;, and it’s time-consuming, error-prone, and developer-unfriendly.&lt;/p&gt;

&lt;p&gt;Recently in one of my own modules I realized I had &lt;a href=&quot;https://drupal.org/node/2100531&quot;&gt;made the same mistake&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The correct approach is to define an api (for example a function like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_move()&lt;/code&gt; in the above example, and call that API function from your form- and GUI-related functions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block_admin_display_form_submit()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The result will be that developers will have as easy a time interacting with your module as human users. This will open the door to third-party interaction, adding value to your module.&lt;/p&gt;

&lt;p&gt;Also, it will allow you to run more automated tests without actually loading pages, which is faster.&lt;/p&gt;
</description>
        
        <pubDate>Mon, 11 Nov 2013 00:00:00 +0000</pubDate>
        <link>http://blog.dcycle.com/blog/27/dont-perform-logic-your-hookformsubmit-use-api/</link>
        <guid isPermaLink="true">http://blog.dcycle.com/blog/27/dont-perform-logic-your-hookformsubmit-use-api/</guid>
        
        <category>blog</category>
        
        <category>planet</category>
        
        
      </item>
      
    
  </channel>
</rss>
