Mar 31 2016
Mar 31
Group photo at MidCamp 2016

MidCamp 2016: 4 days of Midwestererns training on, talking about, sprinting and breathing Drupal. The camp was held in Chicago at the University of Illinois Chicago's medical campus. To get the real midwestern feel, half of our team road-tripped down to the conference. Here's a recap of our time in the Midwest:

read more
Mar 31 2016
Mar 31
Wild geese migrating

This post is based on a talk I gave at DrupalCon Barcelona and this year at MidCamp. You can see a video version of the talk below.

read more
Mar 31 2016
Mar 31

Multilingual websites are complicated, due to the wide variety of ways to convey multilingual content to users. Each multilingual website seems to come with a different set of requirements for how content translation is handled. In this post, I'll take a look at approaches to translating content in Drupal 7 and the new possibilities that the Entity Translation module provides.

read more
Mar 31 2016
Mar 31
City walls built on a rocky foundation

For themers, there are lots of exciting new features in Drupal 8: the Twig tempting system, libraries for loading assets, and the replacement of theme functions with templates. There are also a new set of core themes that come with Drupal 8.

read more
Mar 31 2016
Mar 31
DrupalCon Asia Drupal 8 Theming training group photo

We had a blast going to Mumbai for DrupalCon Asia. On day 1 of the conference, we presented our new Drupal 8 Theming training for a group of 50 first-time DrupalCon attendees.

read more
Mar 31 2016
Mar 31
Workshop with many tools

Drupal 8 theming has lots of new features, one of the most exciting is the addition of libraries. You can use libraries to add stylesheets and javascript to your theme, globally or in particular situations.

read more
Mar 31 2016
Mar 31

It's 2015, we are already in the "Mobile Era" and we all love how our modern sites fit and adapt to any screen. It's amazing how a website can stretch to the 52 inches of a Samsung TV and also look good on your mobile phone. But you know what? There are 20 years of "non-mobile" websites out there screaming to be upgraded. And the first word that comes to mind is "redesign".

read more
Mar 31 2016
Mar 31

I needed to create a new webform on a production site recently. But as a dev, I don't have direct access to the production admin backend; I'm only allowed to push code changes and let the client's team migrate them to prod via drush updb. So I'm supposed to export the webform configuration to code, and deploy it via an update hook, but how?

read more
Mar 31 2016
Mar 31

The web is full of information! Your web sites probably already use many APIs for maps, Twitter, IP geolocation, and more. But what about data that's on the web, but doesn't have a readily available API?

read more
Mar 31 2016
Mar 31
Screenshot of a directory tree.

As a good Drupal developer, one of your New Year's resolutions should be to learn more PHP features. Today, we'll talk about iterating over tree-structured data using the awkwardly-named class RecursiveIteratorIterator.

read more
Mar 29 2016
Mar 29

MidCamp 2016: 4 days of Midwestererns training on, talking about, sprinting and breathing Drupal. The camp was held in Chicago at the University of Illinois Chicago's medical campus. To get the real midwestern feel, half of our team road-tripped down to the conference. Here's a recap of our time in the Midwest:

Trainings!

MidCamp kicked off with a day of trainings. We introduced 25 midcampers to Drupal 8 with our intro Drupal training. We had such a great time meeting all these friendly folks at MidCamp that we're coming back to Chicago May 19-20 to lead a Drupal 8 Theming Training.

Evolving Web training handout - Are you ready for Drupal 8?

Sessions!

I gave a talk for site builders about Migrating Content to Drupal 7. I've since turned that talk into a tutorial with a video and links to the session resources.

Suzanne leading the Content Migration talk

Our front-end developer Jorge Diaz provided a case study about Moving the Evolving Web Site to Drupal 8. And our senior Drupal developer Dave Vasilevsky presented our SiteDiff tool at his talk about Test-Driven Drupal Upgrades.

Jorge and Dave presenting at MidCamp 2016

One of the highlights of the camp was Project Management: The Musical, starring project manager Allison Manley, which leads you through the process of managing a Drupal project with musical flourishes. We also liked Dwayne McDaniel's Sales and Pricing Strategies talk and the entertaining DevOps and the Chocolate Factory about setting up a workflow with Ansible, Vagrant, Behat, and CircleCI. There were lots of other great sessions, all the videos are up on the MidCamp website.

Sprints!

Jorge participated in his first Drupal code sprint. He and Dave spent the day testing Drupal 8 and working on bugs in Bootstrap and working in the i18n node migrate issue queue.

Jorge and Dave sprinting at MidCamp

There were over 40 people at the sprints including a lot of core Drupal front-end maintainers: Lauri Eskola, JoelPittet, MortenDK, David Hernandez, and others.

Sprint task cards

Thanks!

Huge thanks to the MidCamp organizing team, especially Cathy Theys who recruited us to sponsor, train, and speak at the conference.

Mar 28 2016
Mar 28

This post is based on a talk I gave at DrupalCon Barcelona and this year at MidCamp. You can see a video version of the talk below.

Content is at the core of any successful Drupal project. Before you're working with real content, everything is guesswork: you can't be sure that your content types are configured correctly. At Evolving Web, we integrate content migration into the site building process for most of our projects. This way, we can populate a website with real content quickly and efficiently, to get the client involved early on in creating content for the site. We can also fix issues with content structure and identify missing content well in advance of site launch.

There's are many ways to get content into Drupal:

  • Hire a team of students or interns
  • Use the Feeds module
  • Write a custom script, or
  • Create a migration with the Migrate module

Migrate is a tool for getting your data from a source (CSV, JSON file, another Drupal site, a third-party database) into Drupal. This tutorial provides an overview of how to create the most basic migration from a CSV file to a Drupal 7 website. While the code is Drupal 7 specific, the concepts and advice about preparing and testing content could be applicable to any content migration

Let's say you're creating a university website and you need to migrate a set of program data into Drupal. You start by creating a spreadsheet (or asking your client to prepare of spreadsheet) of programs. Then, you create a program content type and write a import the data into Drupal. Here are the steps you might take to make that happen:

1. Create the CSV

  • Provide the content creators with a sample spreadsheet to start with based on your conception of the content and the design/wireframes.
  • Add a column for each field value (e.g. the program description) or field attribute (e.g. image alt text).
  • Have your client create a small set of content (maybe 3-5 representative programs).
  • Give your client feedback on the CSV and write your migration based on this small set of data before asking for the complete data.

Programs CSV

2. Create the Content Type

Next, it's time to set up our content type. We need to create a content type that matches the data in our CSV file. Some things to keep in mind when creating your content type:

  • If you have more than one value in your content, create a multi-value field.
  • If the field has more than one part (e.g. an image with an alt text, a link with a URL and a title), make sure that this lines up with your configuration.
  • If you're creating a List text field, make sure the keys match what the client has entered in the spreadsheet (otherwise, you'll have to transform the values in the spreadsheet in the prepareRow function in your migration).

Program Content Type

3. Create the Migration

Now that we have the CSV and the content type, we can create the migration.

To create a migration, you need to create a simple module that extends the migrate module (so make sure migrate is enabled on your site). 

Beyond your standard .info file, some code in the .module file that tells Drupal that you want to create a migration (or several migrations), the main chunk of code you'll nee to write is the migration itself. In my sample module, I put this code into a separate migrate_programs.migrate.inc file. You can find the example module in its entirety here.

First, we create a MigratePrograms class (this has to match the name we gave our migration in our .module file). Then, we describe our migration and indicate a unique identifier for our content.

class MigratePrograms extends Migration {
  public function __construct($arguments) {
    parent::__construct($arguments);
    $this->description = t('Import programs from a source CSV file.');
    // Tell migrate how to identify the content.
    // We need to tell migrate about the numeric index of each field:
    $this->map = new MigrateSQLMap(
    $this->machineName,
      array(
        'id' => array(
          'type' => 'int',
          'not null' => TRUE,
        ),
      ),
      MigrateDestinationNode::getKeySchema()
    );

Secondly, we tell the migration the destination where our content is going. In this case, it's our program content type.

 // Tell migrate about the destination.
    $this->destination = new MigrateDestinationNode(
      'program',
      MigrateDestinationNode::options('und', 'full_html')
    );

Then, we tell Migrate the source of our migration. Because we're using a CSV file, we identify all the columns of the CSV that we want to migrate into Drupal. Then, we tell the migration where our source CSV lives.

    // Tell migrate about the source.
    $csvcolumns = array(
      0 => array('id', 'ID'),
      1 => array('title', 'Title'),
      2 => array('body', 'Body'),
      3 => array('program_level', 'Program Level'),
      4 => array('program_type', 'Program Type'),
      5 => array('program_image', 'Program Image'),
      6 => array('program_image_alt', 'Program Image Alt'),
      7 => array('tags', 'Tags'),
    );
    $this->source = new MigrateSourceCSV(
      drupal_get_path('module', 'migrate_programs') . '/programs.csv',
      $csvcolumns,
      array('header_rows' => 1)
    );

Finally, we map the destination fields (using the Drupal machine names) to the source (using the identifiers we set up).

    // Map the destination field to the source column.
    $this->addFieldMapping('title', 'title');
    $this->addFieldMapping('body', 'body');
    $this->addFieldMapping('uid')->defaultValue('1');
    $this->addFieldMapping('field_program_level', 'program_level');
    $this->addFieldMapping('field_program_type', 'program_type');
    $this->addFieldMapping('field_program_image', 'program_image');
    $this->addFieldMapping('field_program_image:source_dir')
      ->defaultValue(drupal_get_path('module', 'migrate_programs') . '/program-images');
    $this->addFieldMapping('field_program_image:alt', 'program_image_alt');
    $this->addFieldMapping('field_tags', 'tags');
    $this->addFieldMapping('field_tags:create_term')
      ->defaultValue(TRUE);
    $this->addFieldMapping('field_tags:ignore_case')
     ->defaultValue(TRUE);
  }

Finally, we use a function called prepareRow to break up our comma-separated tags and program type fields into arrays.

  // Break up our comma-separated tags and program types into arrays. These are 
  // our multi-valued fields.
  public function prepareRow($row) {
    $row->tags = explode(',', $row->tags);
    $row->program_type = explode(',', $row->program_type);
  }
}

4. Run the Migration

Finally, we can run the migration to import the content. You can go to Content > Migrate to run the migrations using the UI. Or, you can use these handy drush migrate commands, such as:

drush migrate-register programs
drush migrate-import programs
drush migrate-import programs --update
drush migrate-rollback programs
drush migrate-stop programs
drush migrate-analyze programs
drush migrate-deregister programs

5. Test and Iterate

After running the migrations, it's important to test the content. Some suggestions on testing your content:

  • Use Views to create a table of content to test the results.
  • Test a random sampling of content.
  • Rollback the migrations and try again, when you find issues with the content or migration code.

Testing content migration data

When you're looking at the test content, some things to check for:

  • Missing fields that are required
  • Images that are the wrong size
  • Values for select fields that don’t match allowed values
  • Text that is too long or too short for the design
  • Duplicate titles that make finding content difficult
  • Missing data because the ‘unique ID’ is not unique

Resources

You can download the example migration code and the export of the program content type (use the Bundle Copy module to import this into your site). For more advanced examples, check out the example migrations that come with the migrate module.

[embedded content]
Mar 14 2016
Mar 14

For themers, there are lots of exciting new features in Drupal 8: the Twig tempting system, libraries for loading assets, and the replacement of theme functions with templates.

There are also some new core themes that come with Drupal 8. Open up the core/themes folder and you’ll see the list:

  • Bartik (our trusty default theme)
  • Classy (new!)
  • Seven (still the admin theme for D8)
  • Stable (what’s this?)
  • Stark (basically the same as in D7)

The interesting two that you’ll notice are Classy and Stable. These don’t show up on our ‘Appearance’ page like the other themes (this is because they are set to be ‘hidden’ in their .info.yml file). Instead, they’re meant to be used as base themes when you build a custom theme.

In fact the ‘stable’ theme is the de facto base theme for Drupal 8. Even if you don’t tell Drupal you want to use it as the base theme, it will be used automatically.

The ‘classy’ theme is the base theme to use if you want to start with a version of Drupal core with more classes. For example, if you want to have body classes available that indicate whether the current user is logged in, whether the current page is the front page, and what the current language of the site is, then classy is the theme for you. If your theme name is mytheme, you’d add the following in mytheme.info.yml:

base theme: classy

If you want to choose to insert classes only when you need them, a more clean approach, then you can omit mentioning any base theme. You’ll be extending the stable theme by default. The templates in the stable theme are identical to those in Drupal core, but all conveniently located in a folder that themers can find.

If you want to use the stable theme as a base theme, you might need to add additional classes to your templates. For example, if you want to add a frontpage class to the body tag, you can do the following:

<body {{ attributes.addClass('frontpage') }}>

Find more about using and manipulating attributes in Drupal 8 templates in the Drupal.org Theming Guide.

Mar 09 2016
Mar 09

We had a blast going to Mumbai for DrupalCon Asia. On day 1 of the conference, we presented our new Drupal 8 Theming training for a group of 50 first-time DrupalCon attendees. Along with me and Alex, we had the help of two local developers: Ravindra Singh and Manjit Singh.

The trainers: Ravindra Singh, Alex Dergachev, Suzanne Dergacheva, and Manjit Singh

The participants came from all over India to attend were extra enthusiastic about diving into Drupal 8. The feedback from the training was really positive. We wished we could have had more time to cover more of the goodness of Drupal 8 theming (for our next Drupal 8 theming training in Ottawa, we've scheduled 2 days). We had a half-hour 'selfie session' at the end of the long day of training. I think everyone was pretty excited about Drupal 8 and being part of the first DrupalCon Asia.

Our class of Drupal enthusiasts

There were a good mix of talks at the Con from international and local attendees. A lot of excitement about Drupal 8 was in the air leading up to the sprints on Sunday.

It was particularly exciting to see Dan Callahan's talk about serviceworke.rs caching and other new JavaScript APIs. And we were inspired to hear about Peter Brownell's approach to building websites that are never "finished".

We send a huge shoutout to the Drupal Association for their work organizing such a successful event. We can't wait for the next DrupalCon Asia, wherever and whenever it takes place.

Teaching our new Drupal 8 Theming course

Mar 03 2016
Mar 03

Drupal 8 theming has lots of new features, one of the most exciting is the addition of libraries. You can use libraries to add stylesheets and javascript to your theme, globally or in particular situations.

Your Theme's .info.yml File

One of the first steps in creating a Drupal 8 theme is to create the .info.yml file. Your info.yml file is just like a Drupal 7 .info file, except we use the yml instead of ini format.

For example, if your theme is called mytheme, you’ll have a mytheme.info.yml file. At a minimum, your theme mytheme.info.yml will contain the following lines that tell Drupal about your theme:

name: My theme
type: theme
description: 'My first Drupal theme'
core: 8.x

Just like in Drupal 7, you can also define regions in your theme if you want to override Drupal’s default regions:

regions:
  header: 'Header'
  primary_menu: 'Primary menu'
  secondary_menu: 'Secondary menu'
  breadcrumb: 'Breadcrumb'
  help: 'Help'
  content: 'Content'
  sidebar_first: 'Sidebar first'
  sidebar_second: 'Sidebar second'
  footer: 'Footer'

In Drupal 7, we would also list all of our theme's site-wide CSS and JS files in our .info file. But in Drupal 8, we'll use libraries to do this instead.

Libraries

Libraries are groups of assets (CSS and JS files) that you might need for your theme or module. You can add stylesheets and javascript files that you want to use across your entire theme using a ‘global library’.

Libraries are defined in a dedicated file. In our theme, this file will be called mytheme.libraries.yml. To create a global styling library and a search-specific styling library, you would do the following:

global-styling:
  css:
    theme:
      css/styles.css: {}
search-styling:
  css:
    theme:
      css/search.css: {}

Each of these libraries loads a single stylesheet.

Your theme will almost certainly have a global library of CSS and Javascript files that are always loaded (like the global-styling library we created above). To identify which libraries need to be loaded on every page of your site, you'll need to list them in your mytheme.info.yml file like this:

libraries:
  - mytheme/global-styling

You might also have some libraries that are only loaded in certain contexts. For example, some search styling that only needs to be loaded when you’re displaying search results. This could be useful if you have a lot of CSS that applies only to search filters and results.

In this case, in your search page template (page--search.html.twig), you can add the following line of code to attach the search-styling library to that template. Now, each time page--search.html.twig is loaded, the Drupal theme system will ensure that your search-styling library is loaded as well.

{{ attach_library(‘mytheme/search-styling’) }}

Dependencies

One more thing to keep in mind about libraries. Unlike Drupal 7, Drupal 8 doesn't load jquery by default. If you're relying on jquery in your custom library, you'll need to list jquery as a dependency. You can use the same technique to add dependencies on other libraries provided by Drupal core or contrib modules.

search-styling:
  css:
    theme:
      css/search.css: {}
  dependencies:
  - core/jquery

Summing it up

In Drupal 7, we used drupal_add_css to add particular CSS/JS to certain sections of a site, but libraries give us a more controlled and standardized way of doing this. When you’re looking at a new theme, you'll be able to tell at a glance which libraries it defines. Libraries also help to to think through the different contexts that our design needs to implement (e.g. the home page, the search section, the blog). This should prompt us to organize our theme's CSS and JS accordingly so we can minimize the amount of assets loaded on each page.

Dec 09 2015
Dec 09

It's 2015, we are already in the "Mobile Era" and we all love how our modern sites fit and adapt to any screen. It's amazing how a website can stretch to the 52 inches of a Samsung TV and also look good on your mobile phone. But you know what? There are 20 years of "non-mobile" websites out there screaming to be upgraded. And the first word that comes to mind is "redesign".

Redesign means Higher Cost

Mobile OS manufacturers tend to improve their browsers to allow resizing, double tap zooming and dragging around, so we can use these non-mobile websites on mobile. But this is just a usability workaround, and only fixes half of the "browser + site = UX" equation. If the businesses behind old sites want to upgrade their "non adaptive" websites, they will need to pay for it. If they have a good budget, they will have a lot of options, but what if they don't?

Instead of leaving websites to "die" because they were made more than 4 years ago, why don't we just do something beneficial for both of us: business owners and web developers? Come on!!! Many "family owned businesses" during decades cannot just disappear because we, 2015 Web Developers, prefer to create things from scratch.

Technology is created for pushing, but we can also use it for pulling

I won't say here "Work for free" or "charge less", but I will surely encourage you to "work less" for the same hourly/rate, and work only for that missing part of the website of your customer: The mobile/tablet layer.

Sass is just the future of CSS. It is the new way of saving time, doing more with less (funny huh!) and for creating mixins and functions to save weeks of time. Many CSS libraries/frameworks out there are moving into SASS such as Bootstrap, Foundation and Bourbon Neat, just to mention the 3 most used.

So my intention here is to show how, using SASS & Bourbon Neat, we can target small screen sizes and leave "untouched" the desktop display those sites were designed for.

The following code sets a SaSS mixin that can be used as a function to style only specific Viewport ranges.


// ADD THIS AT THE BEGINNING OF YOUR _config.scss FILE
// ViewPort Breaks
$zero: 0px;
$mobile: 320px;
$tablet: 788px;
$desktop: 990px;
// Helper Media Range function for specific frames
@mixin media-range($range1, $range2) {
  @media all and (min-width: $range1) and (max-width: $range2 - 1) {
    @content;
  }
}

With those 9 lines of code, we can open the door for a lot of websites that need to look nicer and adjust their UI components for smaller screens using mostly fluid widths and some other tweaks.

Then, we will just need to style all "layout responsible" elements, re-using their current CSS and selectors, with the following code:


/* MOBILE and SMALL SCREEN STYLE FIXES */
@include media-range($zero, $mobile) {
  /* Add your styles here */
}

/* TABLET and MID-SIZE SCREEN STYLE FIXES */
@include media-range($mobile, $tablet) {
  /* Add your styles here */
}

I know there are many pieces left that you might have to address: navigation, tables, Flash objects ... each site is a different "patient". Sometimes there will be parts of the site that you need to hide or re-design completely.

But, instead of weeks of redesign process, coffee, meetings... and time, let's help small businesses fix the low-hanging fruit of their non-mobile websites.

Oct 20 2015
Oct 20

I needed to create a new webform on a production site recently. But as a dev, I don't have direct access to the production admin backend; I'm only allowed to push code changes and let the client's team migrate them to prod via drush updb. So I'm supposed to export the webform configuration to code, and deploy it via an update hook, but how?

Because webform nodes are content not configuration, the Features module doesn't help. We generally use the Migrate module with CSVs for content staging, but the thought of exporting arbitrary webform config to CSV gives me the heebie jeebies. The happy medium is node_export, that old-school module that gives a simple UI to export, copy-and-paste, and import a node's configuration across two sites. And using it would work, but would require maintaining its 300 lines of code FOREVER after, just for one silly webform. Shouldn't it be simpler?

I found an even simpler module called webform_share that achieves the same serialization/deserialization webform config in just 200 lines of code. Looking through its source code, the serialization part is quite simple. On the export side, it boils down to this:

$node = node_load(DEV_SITE_NID);
var_export($node->webform);

And on the import side, to process the config you've pasted in:

$node = node_load(PROD_SITE_NID);
$node->webform = PASTED_CONFIG;
node_save($node);

It dawned on me I can just stick the export part into drush php-eval:

drush ev 'var_export(node_load(DEV_SITE_NID)->webform);'

For the record, the output looks like this.

To import the serialized form onto the prod db, we just create the following update hook in one of our custom modules:

/**
 * Imports the Contact Us webform from development.
 *
 * Implements hook_update_N().
 */
function custommodule_7001() {
  $webform = array( PASTE IN STUFF FROM YOUR EXPORT);
  // clean out exported nids that are hard-coded, apparently
  unset($webform['nid']);

  $node = new stdClass();
  $node->type = 'webform';
  node_object_prepare($node);
  $node->title    = 'Contact Us';
  $node->path = array('alias' => 'contact-us');
  $node->language = LANGUAGE_NONE;
  $node->webform = $webform;
  node_save($node);
}

It runs great and requires no extra modules to maintain. We'll be using this trick a lot going forward!

Jan 12 2015
Jan 12

The web is full of information! Your websites probably use APIs for maps, Twitter, IP geolocation, and more. But what about data that's on the web, but which doesn't have an API available?

Suppose we wanted to display on our website a list of all the players in the NHL, and how many goals they've scored. The information we need is clearly on the web. But there's no public API that I can find.

Getting started

To fetch the data without an API, we'll use a technique called 'web scraping'. We'll just fetch the web page that contains our data, and then parse the data out of the resulting HTML. Here's a URL that has the top scorers. If we view the page's HTML source, we can see that the data we want is in a table with class "data". Each row represents one player, with the player's name in column one (counting from zero), and the number of goals scored in column five.

We'll use PHP's built-in DOMDocument to parse the HTML, and DOMXPath to locate things within the page:

// From raw HTML, return an array that maps players' names to goals
function nhl_goals_scrape($html) {
  // Parse the HTML.
  $doc = new DOMDocument();
  @$doc->loadHTML($html);

  // Find a tbody element that's within a table that has class "data".
  $xpath = new DOMXPath($doc);
  $tbody = $xpath->query('//table[contains(@class, "data")]/tbody')->item(0);

  // Look at each row of the table.
  $return = array();
  foreach ($tbody->childNodes as $tr) {

    // Pull out column 1 and column 5.
    $name = $tr->childNodes->item(1)->textContent;
    $goals = intval($tr->childNodes->item(5)->textContent);
    $return[$name] = $goals;
  }

  return $return;
}

$html = file_get_contents('http://www.nhl.com/ice/playerstats.htm?fetchKey=20152ALLSASALL&viewName=goals&sort=goals&pg=1');
print_r(nhl_goals_scrape($html));

We've got our data! It looks like this:

Array
(
    [Tyler Seguin] => 26
    [Rick Nash] => 25
    [Vladimir Tarasenko] => 22
    < snip 26 players >
    [Zach Parise] => 14
)

Scraping multiple pages

There's a problem, however—there's only thirty players on that page. What if we want more than that? It's clear that if we change the pg= part of the URL, we'll get a different page. To get more results, let's just fetch a whole bunch of pages:

// Get the URL for a page of NHL goal scorers.
function nhl_goals_url($page) {
  return sprintf('http://www.nhl.com/ice/playerstats.htm?fetchKey=20152ALLSASALL&viewName=goals&sort=goals&pg=%s', $page);
}

function nhl_goals_many($pages) {
  $return = array();

  // Fetch URLs one at a time.
  for ($i = 1; $i <= $pages; ++$i) {
    $url = nhl_goals_url($i);
    $html = file_get_contents($url);
    $scraped = nhl_goals_scrape($html);
    $return += $scraped;
  }
  return $return;
}

print_r(nhl_goals_many(10));

Now we get even more output:

Array
(
    [Tyler Seguin] => 26
    [Rick Nash] => 25
    [Vladimir Tarasenko] => 22
    < snip many more players, 296 of them! >
    [Alexander Edler] => 4
)

However, this process was quite slow. It takes six seconds on my computer to fetch just ten pages!

Getting up to speed

The reason it's so slow is because we're getting the pages sequentially. Every time we fetch a page, we wait for the request to get to the nhl.com server, for the server to respond, and then for the response to get back–only then do we request the next page. It would be much faster if we could just send many requests at once, and then wait for them all to come back. You might think you can only do that with a language specifically designed for parallelism, like node.js. But it's easy to do in PHP as well!

We'll use the RollingCurl library. We can just download RollingCurl.php and Request.php into a nearby directory, so it's available to our code. Then we'll instantiate a RollingCurl object, and set a callback on it to save the response to each URL request. We'll request all the URLs at once, and when they're all done, we'll just merge all the responses together:

// Pull in RollingCurl
require_once 'include/Request.php';
require_once 'include/RollingCurl.php';

function nhl_goals_rolling($pages) {
  $rolling = new \RollingCurl\RollingCurl();

  // Create a list of URLs, and add each one to our RollingCurl.
  $urls = array();
  for ($i = 1; $i <= $pages; ++$i) {
    $url = nhl_goals_url($i);
    $urls[] = $url;
    $rolling->get($url);
  }

  // Store the result for each URL, as responses come in.
  $results = array();
  $rolling->setCallback(function($req, $rolling) use (&$results) {
    $html = $req->getResponseText();
    $scraped = nhl_goals_scrape($html);
    $results[$req->getUrl()] = $scraped;
  });

  // Run all the URL requests at once.
  $rolling->execute();

  // Collate results.
  $return = array();
  foreach ($urls as $url) {
    $return = array_merge($return, $results[$url]);
  }
  return $return;
}

print_r(nhl_goals_rolling(10));

We get the same output as before, but now it takes only two seconds—much better! You can apply this technique to any other paged web site that holds data you need. Just please be a good web citizen and respect the site you're scraping; don't hit it with too many requests.

Here's a complete Drupal module using this example, so you can try it out at home:
Screenshot of Drupal module

Other options

If you're interested in making fast web requests from PHP, here are some other options:

  • The curl_multi_* functions are what RollingCurl uses internally. They work, but they're not so easy to use.
  • PHP-multi-curl is another wrapper around curl_multi_*. It's similar to RollingCurl.
  • The httprl module provides a parallel request API for Drupal. You might like this if you'll only be using your code in Drupal sites.

I personally favour RollingCurl, though. It has both an API and implementation that are simple and comprehensible. It also limits the number of requests that are in-flight at the same time, so you don't accidentally run a Denial-of-Service attack on the server you're hitting! Finally, it has a Request::setExtraInfo() function to associate arbitrary data with each request, which can help you keep track of all the different responses, even though they arrive out-of-order.

Which do you prefer?

Jan 06 2015
Jan 06

As a good Drupal developer, one of your New Year's resolutions should be to learn more PHP features. Today, we'll talk about iterating over tree-structured data using the awkwardly-named class RecursiveIteratorIterator.

When it comes to arrays, PHP is chock-full of useful tools that you already know about, like
in_array, array_map, and preg_grep. But what about tree structures? They show up all over the place: directories on your filesystem, Drupal menus, hierarchical taxonomy terms, DOM trees, and more. You can process them with loops and recursion but it gets pretty complex—just look at the source for file_scan_directory to see what I mean. Wouldn't it be easier if you could just use PHP's array functions on trees?

We'll use the built-in RecursiveIteratorIterator to flatten tree structures, and make them much easier to process. You just provide the RecursiveIteratorIterator constructor a RecursiveIterator, which tells it how to traverse the particular tree in question—for example RecursiveDirectoryIterator knows how to recurse into directories. Then you can iterate over the entire hierarchical structure in just a simple foreach loop! Here's an example:

// Create a RecursiveIterator that knows how to follow subdirectories.
$recursive_iter = new RecursiveDirectoryIterator('.', FilesystemIterator::SKIP_DOTS);

// Pass the RecursiveIterator to the constructor of RecursiveIteratorIterator.
$recursive_iter_iter = new RecursiveIteratorIterator(
  $recursive_iter,
  // Also pass in a 'mode', to specify whether parents should come before children,
  // after children, or not at all. We want parents first, so we use SELF_FIRST.
  RecursiveIteratorIterator::SELF_FIRST
);

// Use our RecursiveIteratorIterator as if it was a flat array.
foreach ($recursive_iter_iter as $path => $info) {
  print "$path\n";
}

// Or process it with standard array functions!
$pngs = preg_grep('/\.png$/', iterator_to_array($recursive_iter_iter));
foreach ($pngs as $path => $info) {
  print "png: $path\n";
}

Menus in Drupal are also trees, and they can be pretty complex to process! So let's try using the same approach as above, but this time for menus.

Unfortunately, there's no built-in RecursiveIterator for menu trees, like RecursiveDirectoryIterator is directories. So we have to build our own instead, by subclassing an existing iterator, and implementing getChildren() and hasChildren(). That's a bit of work, but then we can easily process a complete hierachical menu in just a few lines of code:

<?php

// A RecursiveIterator that knows how to recurse into Drupal menu trees.
class MenuIterator extends ArrayIterator implements RecursiveIterator {

  // Get a recursive iterator over the children of this item.
  public function getChildren() {
    $link_data = $this->current();
    return new MenuIterator($link_data['below']);
  }

  // Does this item have children?
  public function hasChildren() {
    $link_data = $this->current();
    return !empty($link_data['below']);
  }
}

// Now let's use our new class to print out a pretty list of menu items:

// Get a menu tree.
$menu = drush_get_option('menu');
$tree = menu_build_tree($menu);

// Create a MenuIterator over the tree.
$menu_iterator = new MenuIterator($tree);

// Create a RecursiveIteratorIterator, passing in our MenuIterator.
$recursive_iter_iter = new RecursiveIteratorIterator(
  $menu_iterator,
  RecursiveIteratorIterator::SELF_FIRST
);

// Iterate over the whole tree, as if it was flat.
foreach ($recursive_iter_iter` as $link_data) {
  $link = $link_data['link'];
  if ($link['hidden'] == 0) { // Ignore hidden items.
    $prefix = str_repeat('  ', $link['depth'] - 1); // Make our output pretty.
    printf("%-40s  %s\n", $prefix . $link['title'], $link['link_path']);
  }
}

Here's an example of running this script:

$ drush --user=1 php-script menu-tree.php --menu=management
Administration                            admin
  Dashboard                               admin/dashboard
  Content                                 admin/content
    Comments                              admin/content/comment
  Structure                               admin/structure
    Blocks                                admin/structure/block
    Content types                         admin/structure/types
    Menus                                 admin/structure/menu
      Main menu                           admin/structure/menu/manage/main-menu
      Management                          admin/structure/menu/manage/management
      Navigation                          admin/structure/menu/manage/navigation
      User menu                           admin/structure/menu/manage/user-menu
    Taxonomy                              admin/structure/taxonomy
  Appearance                              admin/appearance
[snip]

This is just the tip of the iceberg. There are so many other trees of data in the world that can be processed with RecursiveIteratorIterator: XML, IMAP folders, HTML elements and more. Let us know what uses you find for RecursiveIteratorIterator!

Dec 31 2014
Dec 31

Drupal allow you to set up installation profiles to fast-track creating a website. Rather than starting from scratch each time you create a site, you can select an install profile that does some initial configuration for you. This is super useful if you make a lot of websites that start the same way. I think multilingual websites are a good example, since there's a lot of configuration that gets repeated.

You’ve used an installation profile if you’ve ever installed a Drupal site. There are two that come with Drupal core: minimal and standard. The minimal install profile doesn't come with much, but the standard one adds a couple content types (articles, basic pages) a user role (administrator), places some blocks in various regions, and sets up text formats as well as a few other things.

Taking the standard install as a model, you can see how it's possible to create your own install profile to automate some configuration of your site.

Automating site configuration with a script

Of course there are other ways to automate this process: you could create a post-install script that runs after the installation of Drupal. If you're familiar with Drush, you'll know that you can do a lot of site configuration using drush commands. However, there are limitations.

It's easy to set a variable using Drush, but adding a content type is another matter. To add a content type with Drush, you'd probably create a Feature using the features module and then enable it with Drush. This is fine if you want everything in Features, but since it's hard to 'de-featurize' content types later on, you might be looking for another approach.

Another drawback with a script is that it's harder for site builders to interact with. With an install profile, you can include forms that the user fills out during the install process to determine settings for the installation.

Architecture of an install profile

If you take the install profile route, you can take a look at the standard install profile as a starting point. The install profile I made as a sample can be found on GitHub here: Drupal Multilingual Starterkit. It contains three main files: multilingual_starterkit.info, multilingual_starterkit.install, and multilingual_starterkit.profile. Here's how you can use the three files:

multilingual_starterkit.info

This looks just like a module or theme .info file. It lists the dependencies of the profile - modules that will be installed when the install profile runs.

name = Multilingual Startkerkit
description = Profile for setting up a multilingual website.
core = 7.x
version = 0.1

;Core Dependencies
dependencies[] = block
dependencies[] = color
dependencies[] = comment

multilingual_starterkit.install

In this file, you can implement hook_install(). It allows you to set up the initial configuration for your install profile: set variables, add content types, blocks, and text formats.

multilingual_starterkit.profile

This is where you can define install tasks for the installation profile. Each task can be a step in the install process, where you can collect information from the user installing Drupal.
You define the tasks by implement hook_install_tasks() and returning an array of steps for the installer. Each step corresponds to a callback function (which could be a form that collects settings from the user). Other examples of tasks would be importing the translations for the selected languages, or adding sample content.

You can also use hook_install_tasks_alter() in the .profile file to unset steps from the default install process.

Choosing a language during the installation process

Adding Make Files

If you want to make your install profile really fancy, you can add .make files. Make files indicate where to download everything from to set up your site. So you can start with just the make files and end up with a fully functioning site. You should create two make files for your install profile:

drupal-org-core.make

As the name suggests, this file indicates what to download from Drupal.org, and specifically which versions to download.

core = 7.x
api = 2

projects[drupal][version] = "7.31"

projects[views_bulk_operations][version] = "3.2"
projects[views_bulk_operations][subdir] = "contrib"

projects[admin_menu][version] = "3.0-rc4"
projects[admin_menu][subdir] = "contrib"

projects[admin_views][version] = "1.3"
projects[admin_views][subdir] = "contrib"

projects[ctools][version] = "1.4"
projects[ctools][subdir] = "contrib"

multilingual_starterkit.make

You'll also need a .make file for your install profile. This will download the install profile and any custom modules that you have created for it. As you can see in this example, you can include your drupal-org-core.make file in this file.

api = 2
core = 7.x
; Include the definition for how to build Drupal core directly
includes[] = drupal-org-core.make

;Multilingual Starterkit Install Profile
projects[multilingual_starterkit][download][type] = "git"
projects[multilingual_starterkit][download][url] = "https://github.com/pixelite/drupal-multilingual-starterkit"
projects[multilingual_starterkit][download][branch] = "master"
projects[multilingual_starterkit][type] = "profile"

Now, all you'll need to run is drush make build-multilingual-starterkit.make and the files for your site will be downloaded and ready to install.

This blog post came out of a presentation from DrupalCamp Montreal 2014. You can find the slides for the presentation here.

Dec 01 2014
Dec 01

Drupal 7 gives us the option to include an 'alt text' for each image field. The alt text is used by screen readers or when the image file isn't available. For some organizations with lots of authors, it's hard to get everyone actually using this alt text field. So, sometimes you want to make it required.

Here's how you can set the alt text to be required for all image fields that have alt text on your site. I did this in a custom module called 'mymodule'.

To start off, you'll want to do a form alter for on the image upload form widget. This is the little image field widget inside the node form. In this function, we want to add a process function on each of image form elements that have the alt text enabled. This will allow us to process and validate each of those fields separately.

/*
 * Implements hook_field_widget_form_alter().
 */
function mymodule_field_widget_form_alter(&$element, &$form_state, $context) {
  if ($context['field']['type'] == 'image' && !empty($context['instance']['settings']['alt_field'])) {
    foreach (element_children($element) as $delta) {
      $element[$delta]['#process'][] = 'mymodule_image_field_widget_process';
    }   
  }
}

Inside the mymodule_image_field_widget_process function, you can make changes to the $element, which is the image field form element that we're working with. You might be tempted, like I was, to set the $element['alt']['#required'] to true. This would be the standard approach for making something required using the Drupal form API. In this case, because this is a form widget appearing inside the larger node form, this has the effect of making the required error message appear when the node form is first displayed.

Instead, use the #element_validate property to create a custom validation function for the image field. This way, it will only run after the node form is submitted.

/*
 * Function to help with processing each image field.
 */

function mymodule_image_field_widget_process($element, &$form_state, $form) {
  if ($element['alt']['#access']) {
    $element['alt']['#element_validate'] = array('_image_field_validate_alt_text');
  }
  return $element;
}

Now, we can create a function called _image_field_validate_alt_text, which sets errors if the alt text is not included. I grabbed this function from this thread on Drupal.org - https://www.drupal.org/node/1906264. It checks whether an image file has been uploaded. If it has, it verifies that an alt text has been included. If the alt text is empty, it sets an error on the form.

/*
 * Helper function to validate that alt text is provided for all image fields.
 */

function _image_field_validate_alt_text($element, &$form_state) {
  if (!in_array('file_managed_file_submit', $form_state['triggering_element']['#submit'])) {
    // If the image is not there, we do not check for empty values.
    $parents = $element['#parents'];
    $field = array_pop($parents);
    $image_field = drupal_array_get_nested_value($form_state['input'], $parents);
    // We check for the array key, so that it can be NULL (like if the user
    // submits the form without using the "upload" button).
    if (!array_key_exists($field, $image_field)) {
      return;
    }   
    // Check if field is left emtpy.
    elseif (empty($image_field[$field])) {
      form_error($element, t('The field !title is required', array('!title' => $element['#title'])));
      return;
    }   
  }
}

Of course, you could change this to make the 'title' field required as well, or make other modifications to these fields.

Apr 28 2014
Apr 28

DrupalCon Austin promises to be a great event! The North American DrupalCon is always the biggest Drupal event of the year, which means a lot of content to absorb, people to meet, and ideas to share. There are also opportunities to contribute to the Drupal project, which is one of the most important parts of the conference.

If you're coming to DrupalCon to expand your knowledge, there are sessions on lots of topics, so it's a great opportunity to soak up a lot of information. However, you might come away feeling somewhat overwhelmed.

One of the best ways to get the most out of the conference is to attend a DrupalCon training. Trainings are scheduled on the first day of the conference (Monday, June 2nd) and go from 9am-5pm, leaving lots of time to learn about a specific aspect of Drupal. Attending a training has lots of benefits:

  • You have the chance to do hands-on learning rather than just listening and taking notes
  • You get to meet and interact with attendees from other organizations, and learn how they're using Drupal
  • Many trainings are focused on specific topics, so you come away with in-depth knowledge of that topic
  • There's a really high trainer to student ratio, so you get to ask lots of questions and get one-on-one help
  • Trainers are Drupal experts, so you can learn from their experience

There's early bird pricing on DrupalCon training tickets until May 2nd, so now is the time to sign up! There are trainings on lots of topics for different skill levels.

Evolving Web is presenting a Views Configuration Training as part of the conference. Views is the ultimate Drupal site building tool. If you have a Drupal website, this training will give you the skills you need to organize and display content the way you want, all through the admin UI.

Apr 22 2014
Apr 22

This year's DrupalCamp NYC was held at the United Nations. The camp was crammed with useful sessions and included a lot of content about Drupal 8. Evolving Web presented a couple talks at the camp, which were captured via Google Hangouts:

Using Panels Wisely

Panels is a popular module for creating landing pages, managing layouts, and developing dynamic pages through the user interface. Panels provides a lot of options for site builders, and you can use Panels in many ways throughout your website (Panels pages, Panelizer, Panels nodes, and mini-panels). This session provides a guide through different ways that Panels can be used, as well as some advice for using Panels in a sustainable way.

[embedded content]

Test-Driven Drupal Upgrade with Docker

Alex presented at the DevOps summit about upgrading your Drupal website in a systematic way, setting up the environment with Docker and writing tests to ensure that nothing was missed in the upgrade process.

[embedded content]

You can find the keynote presentations and other sessions from the summits and the camp on the DrupalCamp NYC YouTube channel.

Curious about what a DrupalCamp at the UN looks like? Checkout our photos from the camp on Google+.

Jan 13 2014
Jan 13

I frequently administer remote servers over SSH, and need to copy data to my clipboard. If the text I want to copy all fits on one screen, then I simply select it with my mouse and press CMD-C, which asks relies on my terminal emulator (xterm2) to throw it to the clipboard.

This isn't practical for larger texts, like when I want to copy the whole contents of a file.

If I had been editing large-file.txt locally, I could easily copy its contents by using the pbcopy command:

cat large-file.txt | pbcopy

In this writeup, I show how we can expose the pbcopy command as a network daemon that listens on port 5556, and is easily accessible from any machine you SSH into.

Daemonizing pbcopy

The quickest way to "networkify" pbcopy is to run the following snippet in a dedicated terminal tab:

while (true); do nc -l 5556 | pbcopy; done

We just asked bash to launch netcat (nc), repeatedly wait for incoming connections on localhost:5556, and pipe any data received into pbcopy.

Now locally, the following two are equivalent:

echo "This text gets sent to clipboard" | pbcopy
echo "This text gets sent to clipboard" | nc localhost 5556

Exposing our daemon to machines we SSH to

For security reasons, our "pbcopy daemon" only allows connections from localhost. But the goal is to allow you to pipe text to your local clipboard from a server you've SSHd into. This is done via SSH's reverse tunnel forwarding feature:

# SSH in to remote-server as usual, except -R asks that 
# remote's port 5556 is forwarded to your laptop's localhost:5556
ssh [email protected] -R 5556:localhost:5556

If you'd prefer to enable reverse tunneling of port 5556 all your future outgoing SSH connections, the following adds the appropriate line to ~/.ssh/config:

echo "RemoteForward 5556 localhost:5556" >> ~/.ssh/config

Having established the SSH reverse tunnel, you can now do the following from the remote server:

cat large-file.txt | nc -q0 localhost 5556
# -q0 is required for GNU's version of netcat to exit on eof; the osx version does it by default

If the remote server is missing nc, either run sudo apt-get install netcat -y or use telnet instead:

cat large-file.txt | telnet localhost 5556

Enjoy your newly-supercharged clipboard!

Getting Fancier

If your laptop is running linux, replacing pbcopy with xcopy should work:

while (true); do nc -l 5556 | xcopy; done

For a more verbose version of our "pbcopy daemon" that prints what's being sent to the clipboard, try this:

while (true); do echo "Waiting..." ;  nc -l 5556 | pbcopy; echo "Copied: "; pbpaste | sed 's/^/  /'; done

To automatically start the "pbcopy daemon" on boot, you should use launchd. See http://seancoates.com/blogs/remote-pbcopy (if down, use Google's cached version)

To expose pbpaste as well as pbcopy, see https://gist.github.com/burke/5960455

Dec 02 2013
Dec 02

If you've used a Retina screen to browse the web or develop a website, you might have noticed that some images look a little fuzzy. Retina devices require images that have twice the number of pixels to appear normally on the screen. This is a bit of a pain for front-end developers. It means that if you want to display a 100x100 image on a website, you also need a 200x200 version of that image retina devices, and then you need to scale it down to size when it's displayed.

When you're making a Drupal website, there are a couple different techniques for this, depending on the type of image.

Images as Content

Your Drupal website is probably already using a bunch of image styles to resize images that are included as content (fields or embedded in HTML). For most image styles, you can fix the sizing issue using a module called Retina Images. This adds a 'retinify' checkbox to each image effect. You just need to select this checkbox and also choose 'allow upscaling' for each image effect. For Retina devices, the module will load an image that is twice as large, but scale it down using the HTML width attribute.

Using Drupal image styles to create Retina images
The only place where this seems to break down is if you're using the Imagecache Actions module to add more image style effects. These effects don't have the setting you need to do retinify the image, so you have to adjust your image sizes to double what they should be, and then fix the displayed size in your CSS. This isn't an ideal solution, because you get larger images that are loaded unnecessarily for non-retina devices. But if your use of imagecache actions is limited or you're not using the module at all, you should be good to go.

Make sure you check all the image styles being used in the site, including those for profile images.

Background Images

For background images, there's a bit more work to do in your theme. The technique is to use media queries to load bigger background images on retina devices. Then you can fix the displayed size using the 'background-size' property.

#main-menu li a {
  background-image: url(../images/image.png);
}
@media
only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and (min--moz-device-pixel-ratio: 2),
only screen and ( -o-min-device-pixel-ratio: 2/1),
only screen and (min-device-pixel-ratio: 2),
only screen and (min-resolution: 192dpi),
only screen and (min-resolution: 2dppx) { 
  #main-menu li a {
    background-image: url(../images/image-v2.png);
    background-size: auto 250px;
  }
}

In this case, the background-size property scales the background image so that it is only 250px tall, with the width being resized automatically. This is appropriate for horizontal sprites, for example if this menu item was the first of several to use the same background image starting at different positions.

This technique requires saving two versions of each of your background images (e.g. image.png and image-v2.png which is twice as big), and adding additional sections to your CSS files. If you're using sprites for your images where possible, this shouldn't be to much additional overhead. This is another good reason to simplify your designs and do more with CSS3 and less with background images.

Other Images

There might be some other cases where you have images being added to your Drupal site that don't fall into these categories. For example, images that are part of your theme like the logo. It's important to review your site using a tool like browserstack.com, so that you can test how images will appear on retina and non-retina devices.

Nov 15 2013
Nov 15

The sixth annual DrupalCamp Montreal was held at the UQAM Coeur de Sciences on October 26th.We had a new format this year, starting with a lively panel discussion on Friday evening about the future of the web, including panelists from the Electronic Frontier Foundation, W3C, and other organizations. In an effort to make the camp more bilingual, simultaneous translation was provided via headsets during the panel discussion. This was followed by a full day of sessions on Saturday on a variety of Drupal topics including site building techniques, Vagrant and Chef, responsive design, and Drupal 8.

The last four years, the camp has been held at McGill University, and the new venue brought in some new faces. There were also more sessions than ever before, with 4 sessions taking place in each time slot.

Evolving Web had five presentations at the camp:

Drupal for Designers

A demonstration of how to set up a Drupal site, from a designer's perspective.

[embedded content]

Vagrant: Getting Started

How to use Vagrant to set up a virtual machine with Drupal and Drush installed.

[embedded content]

Chef for Drupal

Introduction to Chef and how it can be used to manage a production Drupal environment.

Best Practices for Creating a Drupal Website

Including tips for creating/organizing your content types, important configuration settings to check before you launch and a whole bunch of useful modules (slides in French).

Responsive Design with Zen and Zen Grids

How to use Zen Grids to set up layouts on your Drupal site, with an overview of the Zen theme, and how to use Sass and Compass.

Jul 24 2013
Jul 24

The Web Experience Toolkit distribution of Drupal (aka WxT-Drupal, wetkit, or wet-boew) is a version of Drupal designed for organizations with a bilingual and accessibility requirements. Specifically, it's designed for Canadian government and public organizations. It has built with support for English and French, and includes a theme that provides accessibility and responsive support.

How to install the Distribution

There are many methods you can use to install this distribution. If you want to set it up on your local development environment, the easiest way is to use the drush make file provided.
The distribution's documentation and codebase can be found at : https://github.com/wet-boew/wet-boew-drupal. Just scroll down to see the documentation.
If you're using the command line, you'll want to checkout the distribution by running:

git clone https://github.com/wet-boew/wet-boew-drupal.git

Then, go to the directory that's been created and run the drush make file.

cd wet-boew-drupal
drush make --prepare-install --no-gitinfofile --working-copy build-wetkit.make [Directory Where This Distribution Will Live] --yes

For example, if I'm using Acquia Dev Desktop, and I want the files to live under ~/Sites, I would enter

drush make --prepare-install --no-gitinfofile --working-copy build-wetkit.make ~/Sites/wxt --yes

Your installation of the WxT distribution now lives under ~/Sites/wxt.

If you don't have a local development environment and just want a quick way to try out this distro, you can always use the Acquia Dev Desktop tool. You can download it at acquia.com/downloads. Once you have this installed, open up the Control Panel and click on 'Settings' > 'Sites' > 'Import'. Next to Site path, click 'Browse' and choose the directory where the distribution lives (i.e. ~/Sites/wxt). Choose 'Create a new database' and use 'wxt' as the DB name and Subdomain.

Importing a Site using Acquia Dev Desktop

When you visit the site, you'll go through the installation process. First, the WxT profile will be installed:

WxT-Drupal Installation Screen

Next, you'll enter your site information details as usual:

Adding the site information

You'll be prompted to choose a theme. Currently, there is only one theme to choose from. Once the site is installed, you can either create a sub-theme based on this theme or create a new theme to implement a more distinct look-and-feel.

Choosing a theme

Next, the installer will automatically import the French language files and sample content. Finally, you'll see a page that tells you that the installation is complete:

Installation completed screenshot

You can click on 'Visit your new site' to see the results of your work.

Features of the WxT-Drupal Distribution

Now that you have WxT-Drupal installed, there are plenty of features to explore. Here's a short-list of the built-in functionality that the WxT-Drupal distribution sets up for you:

  • Responsive Theme, which implements the Government of Canada look and feel
  • Multilingual UI and Content, with French installed for you
  • Panels & Panoply for creating a managing landing pages
  • Beans, which provides an easy way to set up translatable blocks of content
  • CKEditor, which is a WYSIWYG editor
  • Search API
  • Workbench for drafting content

Of course, this means that there are many modules enabled on the site by default (almost 150). This makes the distribution more complicated than core Drupal. Although there are lots of great features set up for you, learning how to take advantage of these and how to configure the website can take some work.

On August 2nd, Evolving Web will be offering an online training on how to get started using the Web Experience Toolkit distribution. We hope you can join us!

May 15 2013
May 15

Our team has been using nginx for several years, but nobody on our team could say "I know nginx well". Over the years we've been adapting snippets from the nginx wiki and random blog posts, and the often inconsistent structure of our config files reflects this trial-and-error approach. Every 6 months I go on Amazon and see if anyone's written a well-written nginx overview that covers most of the important modules and directives, and includes sample config files to teach best practices. Although there's 2 previous packt books available, their terrible reviews make it clear they're no better than 90% of the schlock that Packt publishes.

Fortunately, Mastering Nginx by Dimitri Aivaliotis couldn't be more different. At 348 carefully-written and edited pages, it manages to provide a great overview to technically-inclined beginners, while covering a lot of advanced topics you'll meet in production. It's telling that one of the technical reviewers is Andrew Alexeev, a co-founder of nginx and the author of the nginx chapter of The Architecture Of Open Source Applications.

As I run a Drupal shop, I was happy to see that it includes meaningful coverage of reverse proxies, php-fpm, and even Drupal specifically. Additionally, I learned a lot looking over the sections on memory tuning, access logs for debugging, and SSL. Based on a quick skim, I can confidently say that this book will teach you a lot about nginx, both as tutorial and reference. It's now mandatory reading for anyone on our team who will ever touch an nginx config file.

Disclosure: I don't generally like Packt books, except awesome ones like Drupal 7 Module Development and Apache Solr 3 Enterprise Search Server. Also, I don't have an affiliate account with amazon.com.

Feb 25 2013
Feb 25

Ottawa held its first DrupalCamp on February 22-23rd. The camp included a summit about Drupal for government, a codesprint, and sessions. Being in Ottawa, the themes for the camp included the Drupal Web Experience Toolkit (WET) distro and adoption of Drupal by the Government of Canada. The WET distro has been adopted by many government departments, and the camp provided a venue for people to discuss progress of and the direction of the distro.

The camp was hosted at the University of Ottawa's new Social Sciences building and had a great turnout at around 200 people, including Drupal shops, representatives from government, and others in the Drupal community.

Evolving Web sponsored the camp and presented a couple sessions:

Site Building Checklist Presentation

I presented a checklist for site builders, which includes clean-up tasks, modules to install, and checkboxes to check before launching a Drupal site. The checklist includes items on the following topics:

  • Cleaning up content types and Views
  • Following best practices for search engine optimization (SEO)
  • Content editing and administrative UI
  • Emails and user account settings
  • Theming
  • Multilingual items
  • Settings to change pre-launch
  • Things to test post-launch

You can take a look at the full checklist. The slides for the presentation are below:

Vagrant for Drupal

Alex Dergachev presented on how to make your development environment setup consistent and sharable using Vagrant. Vagrant is a tools that spins up a new virtual machine based on a pre-configured base image and a set of configuration files and recipes. The presentation included a demo of setting up Vagrant, downloading a base box, and using the most important vagrant commands. He also included an overview of the ruby basics needed to customize Chef recipes. You can take a look at the notes from the presentation, which lists resources for using Vagrant with Drupal.

For those in Ottawa who want to get into Drupal and are looking for more information beyond the camp, Evolving Web offers regular Drupal training sessions in Ottawa. Our upcoming courses in Ottawa are listed in the training section.

Oct 19 2012
Oct 19

Drupal Global Training Days are a recent initiative in the Drupal community, created this year by the Drupal Association to help spread Drupal adoption. The concept is to organize free or at-cost intro Drupal training on a given day, to grow the Drupal community. Drupal companies and local user groups are encouraged to organize a training session in their area as part of the event.

The next Global Training Day is coming up on December 14th, so there's lots of time left to sign up as a trainer, or sign up to get training. In Montreal, Evolving Web is hosting a day-long Hello Drupal Training as part of the event.

The idea behind global training days is to get more people using Drupal: more people building Drupal websites and using Drupal in their schools, businesses, and community organizations. We're looking forward to meeting a bunch of future Drupal enthusiasts and contributors on December 14th!

If you're looking for more in-depth training, we have the following courses scheduled in November and December:

  • Drupal in a Day Training in Toronto (November 14)
  • Drupal for Developers Training in Toronto (November 15)
  • Drupal for Developers Training in Ottawa (December 7)
  • Intro HTML & CSS in Montreal (November 24)
Oct 11 2012
Oct 11

This Saturday is DrupalCamp Montreal 2012! It's not too late to signup through the DrupalCamp website for $20, or at the door for $25. This will be Montreal's fifth camp, and it promises to be an awesome day full of interesting sessions, good networking opportunities, and a fantastic party to wrap up the day. The camp will be held at McGill University at the Schulich School of Music (555 Sherbrooke St. West).

As part of the camp, the Montreal Drupal Association is also organizing a Drupal codesprint on October 12th at Notman House and October 13th at the camp location. The sprint is open to everyone and will include contributions to Drupal 8 multilingual, accessibility, and documentation. If you've never been to a Drupal codesprint before or don't know how to get started contributing back to Drupal, now's your chance.

The camp will feature a keynote session by Amatai Burstein, maintainer of the Organic Groups module and exemplary contributor to Drupal. Amatai is known for his unique presentation style, so the keynote is not to be missed.

Evolving Web will be presenting four sessions at the camp. We'll also be hanging out at the registration desk, so be sure to say hello!

You can see the program of the camp for a full list of sessions. If you're still looking to get involved in the camp, it's not too late! You can propose a BoF (Bird of a Feather session, basically a small group discussion) on any topic or sign up to volunteer.

Aug 20 2012
Aug 20

Recently we needed to import a fairly large dataset into Drupal. There were 200,000 items to import, the data changed relatively frequently, and we needed them to be Drupal entities so that we could make them searchable with Apache Solr. We would normally import data using one of

  • Feeds, which provides a UI for importing a wide variety of data formats
  • Migrate, which makes it easy to write code to import data
  • a custom Drush script, calling entity_save() for each record

We soon realized that no matter which of these options we chose, entity_save() or node_save() would be called 200,000 times. After some experimentation, we decided that this was just too slow for our purposes. We instead decided to write a Drush script to write the data directly to the database. This brought the time to import everything down to about 15 seconds. Obviously this approach isn’t appropriate in most circumstances, so I’ll explain how and why we we did it this way.

When (not) to take this approach

You can do anything you want to your Drupal database. However, editing the node table manually will almost certainly leave you in tears. This is because the node table is closely tied to the field tables and the node revision table, and contrib modules need a chance to run on hook_node_save(). So it’s a bad idea to take this approach to import

  • Nodes
  • Users
  • Taxonomy
  • Anything from core
  • Most contrib entities, unless you understand them really really well

But! If you create your own custom entity which isn’t fieldable, then all of its entity data lives in a single database table in your Drupal database. That means you can do anything you want to it without getting hurt, as long as there aren't any contrib modules that you're depending on to do things for you using hook_entity_save(). The only constraint on this table is that it has to have a numeric primary key for the entity ID.

So this might be for you if

Why we decided to do this

We started out by using the Migrate module together with the Migrate Extras, since we were already using it to import content into Drupal 7 from a Drupal 6 site. The Migrate module was reasonably easy to use, and I enjoyed using it. It’s even reasonably fast, but the fact of the matter is: running entity_save() 200,000 times takes a long time.

We’d already designed our entity so that it used just one database table, and we trusted the data that we were importing to not need any validation, so we decided to import directly to the database. Imports went from 40 minutes to 15 seconds.

How we did it

In a nutshell, the approach we took was to

  1. dump all the data into a table in the Drupal database $temp_table
  2. use that table to populate the as a base table $entity_table for your custom entity type.

We were importing CSV files, so we used a ‘LOAD DATA LOCAL INFILE’ statement to load the data into a temporary table. This statement runs very quickly. After that we ran other SQL statements to copy the data from our temporary table into the table for our custom entity type. We did all the data processing in MySQL, so everything ran extremely quickly. The downside to doing all the processing in SQL statements is that, well, you only have SQL statements available to work with. However, if there isn't much processing to do, then this may not be an issue.

It isn’t advisable to just write pure PHP code and SQL for your import script, though — Drupal provides lots of great database helper functions, such as:

You can put together select and insert queries to avoid having to iterate through all of the records in PHP. Here's a PHP code fragment which illustrates our strategy:

db_create_table($temp_table, $schema);
db_query("
  LOAD DATA LOCAL INFILE '$csv_filename' INTO TABLE $temp_table
  FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
  LINES TERMINATED BY '\\n'
  IGNORE $lines_to_skip LINES;
");
// Move the data from the temporary table into the entity table
$select_query = db_select($temp_table, 'csv')
  ->groupby('country');
$insert_query = db_insert($entity_table)
  ->from($select_query);
// MySQL's UNIX_TIMESTAMP() doesn't support negative timestamps, so do this instead
$select_query->addExpression('timestampdiff(second, from_unixtime(0) , start_date)', 'start_date');
// Add a few constant fields to each row
$select_query->addExpression("'$language'", 'language');
$select_query->addExpression("'$updated_time'", 'updated_time');
$insert_query->execute();
// Drop the temporary table
db_drop_table($temp_table);

Dealing with your data post-import

Once you have your data in a table, you'll probably need to display it on your site somehow. You can install the excellent Entity API module to create views of your data. If you need to make your data searchable with Apache Solr you can tell Apache Solr about your entity type as described in this blog post.

Dumping data directly into the database is a closer-to-the-metal approach and you have to be much more careful about validation. It also means that you have to do a bit more work if you want to update the imported data. It's extremely fast, though, which may make the extra care you need to take worth it.

Aug 14 2012
Aug 14

There will be a hive of activity around multilingual Drupal at DrupalCon Munich 2012! Whether you're new to multilingual Drupal or a developer wanting to change how Drupal works with languages, there are lots of opportunities to get involved.

Multilingual Training

On August 20th, there will be a multilingual training for those new to multilingual site building with Drupal. Learn about the latest techniques and best practices for getting your site up and running in multiple languages. I'll be presenting the training along with Alex Dergachev and Gábor Hojtsy. It's going to be a whirl-wind day of multilingual Drupal learning, so don't miss out!

Code Sprints!

The conference includes a huge code sprint for the Drupal 8 Multilingual Initiative (D8MI). This initiative is led by Gábor Hojtsy and involves improving multilingual support for Drupal 8. Goals include getting entity translation into core and improving the interface for installing and configuring languages. It's an ambitious sprint with lots to be done from writing and testing core patches to writing documentation.

The sprint kicks off on August 18th, 19th, and 20th. There will also be a post-conference sprint on August 24th, 25th, and 26th. On Friday, Aug 24th, the sprint will take place at the conference venue. The venue on the other days is TBA. Sign up for the sprint whether you're participating in person or from home.

DrupalCon Multilingual Code Sprints and Training
Code Sprints and Training at DrupalCon Munich

Sessions

The conference includes a two multilingual-focused talks:

  • My session about Multilingual Content in Drupal 7 and 8 (Tuesday at 3:45pm) will focus on multilingual site building and approaches to content translation. With the entity translation module being adopted as part of Drupal 8, site builders will have more flexibility when it comes to structuring multilingual content.
  • Gábor Hojtsy's session Drupal 8's Multilingual Wonderland (Wednesday at 1pm) is a core conversation covering all the progress and plans for multilingual support in Drupal 8. This should be a great session for those looking to get involved in the post-DrupalCon sprint.

BoFs

So far, there are four multilingual BoFs scheduled:

Overall, it's going to be an exciting conference for Drupal multilingual. If you haven't already, bookmark the events and sessions above and we'll see you in Munich!

Jul 23 2012
Jul 23

In an increasingly global world, Drupal core's support for multilingual content has made it a favourite CMS among large organizations. Despite that, Drupal site-builders and develpoers are often stumped by the myriad of possible approaches and tricks involved in making it work. Over the last few months, Evolving Web has worked together with Acquia to put together a multilingual site building course to walk you through the process step by step and unfuddle much of the complexity.

In the last year, documentation of multilingual modules and core functionality have improved, with more documentation pages on Drupal.org and even a book on the subject by Kristin Pol. However, site builders, gave so many complex decisions that setting up a multilingual site for the first time can be bewildering.

The course will be presented for the first time in Montreal on August 10th and again at DrupalCon Munich on August 20th, and will cover a wide variety of topics including:

  • Setting up a Multilingual Drupal installation
  • Using different methods of content translation
  • Managing UI translation in Drupal
  • Assessing a module's multilingual capabilities
  • Multilingual building blocks: Views, Blocks, Menus, and Panels

For a sneak preview of some of the course materials, we invite you to attend a free Acquia webinar on August 8th, which will cover the content translation process and managing translation strings both in and outside of Drupal.

If you're interested, please sign up for the day-long training session at DrupalCon Munich, which costs 400.00€ and will be presented by Evolving Web and Gábor Hojtsy, the D8 Multilingual Initiative lead.

Hope to see you in Munich. Bis bald!

Mar 20 2012
Mar 20

You're probably reading this blog post right now from Drupal Planet. It's an amazing resource: we use it to keep up with the latest developments in Drupal, find tutorials, and stay connected with the community. However, the limitations of Drupal Planet can be frustrating. On Drupal Planet, you can see some of the latest posts, and a list of feeds, and that's about it. Sometimes it includes the full article, sometimes just a snippet. There's no way to search Drupal Planet, not even with Google.

In the Drupal Community if you have a problem with something, you submit a patch. Drupal Sun is my patch, which we're launching today.

Drupal Sun was inspired by the way tools like Google Reader and Feedly take feeds and make them easy to read. In brief, Drupal Sun allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

This was my first Drupal project. Along the way I learned a lot about Drupal, Solr, javascript, responsive design, and how to design and build a web application. Although there are still exciting features we would like to add, we want you to be able to use and enjoy it right now! I would love to hear your thoughts and ideas.

If you are at DrupalCon Denver, come by Evolving Web's booth, or comment on this blog post and I will get back to you.

Mar 09 2012
Mar 09

#states is a new Form API property in Drupal 7. It makes it easy to change the state (unchecked, visible, enabled, etc.) of an element based on the state of another element. Randy Fay has a great explanation of what it is and how to use it.

For example, to show a settings field only when a checkbox is checked, you can write

<?php
$form
['settings'] = array(
  
'#type' => 'textfield',
  
'#states' => array(
    
'visible' => array(
      
// Element to check => Condition to check
      
':input[name="toggle_me"]' => array('checked' => TRUE),
    ),
  ),
);
?>

It’s an amazing way to make forms interactive without writing any custom Javascript. However, it’s pretty limited in the things you can use as a dependency: you can condition on checkboxes being checked/unchecked, values being empty/nonempty, or a text/select box having a certain value.

In a recent project, we needed to display a ‘Postal/Zip code’ field if a select box had ‘Canada’ or ‘USA’ selected. For now, #states doesn’t support ‘OR’ in Drupal 7. We could have written some custom javascript, but then we thought “well, what if #states supported regular expressions?”




#states doesn’t support regular expressions out of the box. However, Konstantin Käfer’s slides (skip to #59) explain how to extend #states so that it does! Add the following code snippet to a suitable javascript file.

if (Drupal.states) { // Don't crash if states.js hasn't loaded yet
    // Override the 'Object' comparison in states
    Drupal.states.Dependent.comparisons.Object =
        function(reference, value) {
            if ('regex' in reference) {
                return (new RegExp(reference.regex, reference.flags)).test(value);
            } else {
                return reference.indexOf(value) !== false;
            }
        }
}

If your form element ever loads via AJAX, this snippet won't work as is: you need to put it inside a Drupal behavior. You’ll need the Behavior Weights module for this trick to work: this behavior needs to run before the Drupal.behaviors.states behavior.

Drupal.behaviors.statesModification = {
  weight: -10,
  attach: function(context, settings) {
    if (Drupal.states) {
      Drupal.states.Dependent.comparisons.Object =
        function(reference, value) {
          if ('regex' in reference) {
               return (new RegExp(reference.regex, reference.flags)).test(value);
          } else {
            return reference.indexOf(value) !== false;
          }
        }
    } 
}

Now all we need to do is write:

<?php
$form
['#attached']['js'][] = 'your_javascript_file.js';
$form['settings'] = array(
  
'#type' => 'textfield',
  
'#states' => array(
    
'visible' => array(
      
':input[name="country"]' => array('value' => array('regex' => '^(Canada|USA)$')),

    ),
  ),
);

?>

You could also modify this a bit to extend #states in other ways: to check to see if a value is in a list of values (Monkey, Chimpanzee, Gorilla...), for example. Note that since this overrides Drupal.states.Dependent.comparisons.Object, you'll need to do all your extending in the same place.

Mar 02 2012
Mar 02

This week I had the chance to speak at ConFoo, Montreal's annual conference on web technologies. The conference attracts speakers and attendees from across Europe and North America, as well as locals from Montreal. On the list of speakers this year were Andrew Zmievsky who presented on Geotools and ElasticSearch and James Duncan from Joyent speaking about Node.js.

My topic was 'Drupal as a programmer-friendly CMS'. Besides introducing Drupal's features and architecture, I wanted to show attendees why Drupal is a good choice for developers. The conference includes sessions on a variety of programming languages, databases, libraries, and frameworks, but this was the only session dedicated to Drupal at the conference.

Introducing a room full of experienced developers to Drupal in an hour was not an easy task. We've recently started running a one-day Drupal training course for developers, and I tried to fit the essentials from the intro section of that course into a one-hour presentation.

I wanted to convey Drupal's strengths as well as the fact that it's a complex system that has a steep learning curve. I covered the basic Drupal CMS concepts, but also features that are new in Drupal 7 that make it more of a framework: renderable arrays and entities. I highlighted two major contributed modules that make Drupal really powerful: Views and Features.

I also included a 10-minute segment on 'How Drupal Works', which included the bootstrap process, the hook and menu systems, and the form API. The 'Things Developers Like' section included a laundry list of reasons why Drupal is developer-friendly: command-line integration with Drush, coding standards, the Drupal security team, and the Devel module.

Feb 29 2012
Feb 29

Yesterday we posted an article on Drupal Planet about our plans for Drupalcon Denver. Aside from promoting Suzanne's session on i18n site building and the #d8mi codesprint, the post announced our Drupalcon photo contest, in which participants are asked to take a picture of themselves hanging out with other awesome Drupalistas, which for many people is what the conference is all about. To try to make the contest go viral, we gave it an ironic name ("Drupal Celebrity Photo Challenge"), included a pic of our star-struck intern Tavish meeting Dries at Drupalcon London, and asked that all entries are submitted via twitter.

If any publicity is good publicity, then our plan was a resounding success. Over the following day, we received dozens of tweets, emails, and phone calls reacting to the contest, both positively and negatively. Most of the concerns were related to either the potential invasion of privacy (after all, rock stars can't stand paparazzi), or the exclusionary nature of the notion of "celebrity", and are eloquently summarized by Tiffany Farriss's tweet:

#Drupal people, while cute and powerful, are not Pokemon. Do not try to catch them all. #DrupalCon

Since the last thing we want to do is annoy our friends and colleagues, or risk a DrupalCon controversy, we're going to follow all your good advice and fix our contest before any feathers really get ruffled.

The new photo contest is going to be called "Drupal In Real Life". The premise is simple: take a picture that illustrates how Drupal isn't just PHP code, but a thriving, diverse community of creative and fun-loving Drupalistas, and tweet a link to it tagged with #drupaIRL. Be sure to make it interesting, original, or humorous, since we'll be picking the winner based on the reaction your photo gets on twitter. For more information, see the contest page at evolvingweb.ca/drupalirl or send us a message via Twitter :)

Feb 27 2012
Feb 27

There are only a few weeks left until DrupalCon Denver, and we’re getting pumped up at Evolving Web for the year’s largest Drupal hangout. With an estimated 4,000 participants in mile high Denver, March 19-23th promises to be a week teeming with exciting sessions, networking and skiing! Four of us will be heading down to Denver, and we hope to see you there. We have lots planned for the conference from contests to sessions to code sprints

Update: We've revised our contest, see evolvingweb.ca/drupalirl for the updated version!

DrupalCon Ticket Giveaway

We have one extra ticket for DrupalCon Denver to give away. If you're a Montreal Drupaler who's planning on attending the Drupal 8 Multilingual code sprint in Denver, give us a tweet @evolvingweb and let us know you're interested!

Photo Contest

Following on the heels of our Drupal Crossword in London, comes our Drupal Celebrity Photo Contest! Hunt down your favourite Drupaler for a photo-op, then tweet the picture with hashtag #drupalceleb for a chance to win a camera! More information and contest rules at evolvingweb.ca/drupalceleb. UPDATE: To address concerns raised about privacy, we've modified our photo contest, now called Drupal In Real Life photo challenge. For more info, see the follow-up blog post Drupalcon Contest, Take Two.

Session: Multilingual Site Building with Drupal 7

Curious about what it takes to integrate multilingual support into your website? I'm co-presenting a session on multilingual Drupal with Florian Loretan from Wunderkraut. We'll introduce you to Drupal’s multilingual architecture and help you get off the right foot when planning your next big project. Our session, Don't Get Lost in Translation: Multilingual Site Building with Drupal 7, will take place on Tuesday, March 20th at 3:45pm in Room 203.

Drupal 8 Multilingual Code Sprint

Gabor Hojsty will be leading a Drupal 8 Multilingual Initiative codesprint on the last day of DrupalCon, as well the two days following DrupalCon. There has been a strong push towards greater multilingual support in Drupal 8, and we want to help as much as possible. We'll be at the sprint, so come by and help make this initiative a success. See Gabor’s post on groups.drupal.org for more information.

Silver Sponsors

We’re excited to sponsor DrupalCon Denver at the Silver level this year! Ever since DrupalCon DC, we’ve been showing our support for the conferences by pledging sponsorship at different levels. Join us in Denver at booth 103, where we’ll be hanging out for the week. Come by, say hello and grab some of our awesome swag.

While we’re busy getting ready for DrupalCon, we hope you’ve had a chance to review the schedule and fit in your favourite sessions. Tickets are still available, so get yours now. See you in Denver!

Feb 21 2012
Feb 21

Entities in Drupal 7 simplify and centralize common logic that, in earlier versions of Drupal, was duplicated in several places, or only usable in specific modules. There are, however, many features (such as saving entities!) that did not make it into Drupal 7. The Entity API module fills in these missing gaps in the core and goes a step farther by simplifying definition of new entity types, and integration with contrib modules such as Views, Rules, and Features.

Generalized API

Core provides standardized entity loading using entity_load. There are however no functions to save, view, or delete entities! The Entity module defines entity_save, entity_view, and entity_delete functions. These new functions provide the other operations that are critical when code needs to work generally for multiple entity types. Normally you have to implement your own logic for saving an entity, and hope that you are properly invoking all the right hooks, but now you don’t have to worry about writing that yourself.

Clean Object-Oriented Design

All Drupal core entities are stdClass objects. The Entity API defines an Entity class which encapsulates the entity's properties (such as its type) into one object, and wraps many of the entity operation functions such as entity_save(), entity_label(), entity_uri(), entity_i18n_string(), etc. into methods. For example, instead of writing entity_save($entity_type, $obj), you can just write $obj->save(). You can of course extend this class when defining your own entities, which is what we’ve often chosen to do.

Another general upside to using classes in Drupal 7 is that files containing classes can be automatically loaded when they are needed, so any complex logic that you might need for your class will only need to be loaded on pages where the entity is actually used. This can be a plus for performance since it decreases the amount of code loaded on each page.

Automatic CRUD UI

No one likes building complex menu structures. Thankfully, the Entity module simplifies creation of standard UIs for managing entities. All you have to do is give it a base path for the interface and define the edit form, and from there the module can create add, edit and listing pages automatically, so you don’t have to.

Views and Rules integration

Integrating with other modules can be complicated, so the Entity module includes automatic integration with several modules, including Views and Rules. It will automatically give Views data about the structure of your entity by using the database schema or the entity properties. Similarly, because the loading and saving is done via the Entity module, it can automatically trigger events in the Rules module.

Exporting and Features integration

Exporting objects and configuration into code is a huge plus for both site deployment, and change tracking. The Entity module provides functions to export entities to JSON, and cleanly integrates with Features. To export a custom entity type, all that you need to do is use a specific entity controller, and define required exportable fields such as 'machine name' and ‘status’.

Standardized definition of properties

The same data that is used to provide integration with Views is available for any module to use. The Entity module provides the hooks hook_entity_property_info(_alter) ( see also ) for defining information about an entity type's properties. This property information includes the name along with metadata like data type, label and description, and if the value is required or needs to be sanitized.

This property data can be automatically generated by inspecting the entity's database table, or you can manually specify properties using hook_entity_property_info for more advanced behavior. Along with integration with Views, this data can then be used to automatically sanitize string data, or format numbers, or reference other tables.

Validation

Building on the Entity module's property definitions, you can specify validation callback functions to validate data for properties. Heavy reliance on the Form API validation in core and contrib means that it is often very hard to validate an entity programmatically and the only real option is to re-execute the form behind the scenes. By having metadata about properties easily available, the values can be validated properly without having to work through the Form API at all.

Conclusion

I hope this has motivated you a bit to take a look at the Entity module. Usage of the module is growing daily and you may already have it installed as a dependency for a contrib module. Several high-profile contrib modules, such as Organic Groups, Commerce, Rules, Search API and Field Collection, have the Entity module as a dependency and there are more all the time.

Pages

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web