May 31, 2014

Applying a very simple template for theming a form

Drupal 8 uses Twig as its underlying theming engine, so there are some significant differences in how to apply template files for theming the forms that I've implemented and described in a previous post.

In this post I describe the changes I made to implement the use of a very simple template that in the D7 version contains no PHP except a call to drupal_render_children(). This post should serve as a very brief intro to the use of Twig and what changes are entailed.

First, as in D7, hook_theme needs to be implemented in the .modules file in order to define a hook, which in this case is implemented with a template file. The hook definition is essentially the same as in D7. The  path  property is not needed as long as the template file is placed in the  templates subdirectory under the module root.
function optimizely_theme($existing, $type, $theme, 
                          $path) {
  return array(

    'optimizely_account_settings_form' => array(
      'render element' => 'form',
      'template' 

        => 'optimizely-account-settings-form',
    ),

  );
}
Second, the template file is named optimizely-account-settings-form.html.twig instead of optimizely-account-settings-form.tpl.php. Its contents are:
<p>In order to use this module, ... </p>
<p>Most of the configuration and ... </p>
{{ form }}
The last line is Twig syntax to print the contents of the variable form. The line replaces the following in the D7 version.
<?php echo drupal_render_children($form) ?>
Finally, the form array returned by the  buildForm method must have property  #theme  added but is otherwise the same as in D7.
public function buildForm(array $form,
                          array &$form_state) {
  $settings_form['#theme']
    = 'optimizely_account_settings_form';
  $settings_form['optimizely_id'] = array(
    '#type' => 'textfield',
    '#title' => $this->t('Optimizely ID Number'),
    // Other properties of the textfield ...
  );
  $settings_form['actions']
    = array('#type' => 'actions', );
  $settings_form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Submit',

  );
  return $settings_form;
}
Sources:

DrupalTwig/sandwich  code repo
https://github.com/DrupalTwig/sandwich
Example of using Twig theming engine, with the _content property in the routing file.

Twig best practices - preprocess functions and templates
https://drupal.org/node/1920746


May 29, 2014

Git, line endings, and the .gitattributes file

While trying to solve a problem with block literals in YAML files, I discovered that Git may convert line endings when text files are committed into the repo or when they are checked out into the working directory.

Specifically, on my Windows system, by default Git converts the Windows CR-LF endings to Unix-style LF when committing code, and it converts the other way when code is checked out. Besides suspecting that this was causing the problem I was trying to fix, I also found it unsettling that code I was creating was being altered.

My initial solution was to add a .gitattributes file to the root directory of the Optimizely module, which is where I had initialized Git. This file simply contained one line:

*  text  eol=LF

The asterisk is a file wildcard. The line means all files (that Git thinks are textual) are to be treated as text and their line endings are to be LF only.

A .gitattributes file applies to the Git repo for all users. It eliminates the problem of different users possibly having different Git settings.

Later, I found that Drupal 8 core ships with a standard .gitattributes and used that instead. Rather than the overly broad wildcard that I used, the Drupal 8 version carefully lists all the file extensions that should be treated as text as well as those that should be treated as binary. Text files are specified to have LF line endings. You can access the file at the url mentioned below.

N.B. I'm using a text editor, Sublime Text 2, that can render all three kinds of line endings and can readily convert the line endings in a file from one kind to another. So editor limitations are not a consideration for me,

Sources:

Line endings for Drupal in git
http://ithomas.name/2013/line-endings-for-drupal-in-git/
A very brief mention of using .gitattributes files.

Core ships with a .gitattributes file to improve git patches, October 2012
https://drupal.org/node/1803766

The Drupal 8 core .gitattributes file
http://cgit.drupalcode.org/drupal/plain/.gitattributes?id=refs/heads/8.x

Mind the End of Your Line, March 2012
http://adaptivepatchwork.com/2012/03/01/mind-the-end-of-your-line/
This is an excellent, detailed description of managing line endings in Git.

May 27, 2014

Implementing forms

In Drupal 8 implementing forms is very different from D7. In its overall structure D7 primarily uses hook_menu(). In Drupal 8 there are core classes provided from which you derive child classes. Those classes are named in the routing file to connect them with specific paths.

For the Optimizely module, the set of code at this point implements three different tabs, each of which contains a form. Because the code would be a bit lengthy to show in its entirety in this posting, I only provide extracts to illustrate the main points.

First, an extract from the optimizely.routing.yml file.
optimizely.acct_info:
  path: /admin/config/system/optimizely/settings
  defaults:
    _form: \Drupal\optimizely\AccountInfoForm
    _title: Optimizely
  requirements:
    _permission: administer optimizely
 
The change from the previous version of the routing file is that instead of _content the _form property is used. The value of the _form property is the namespaced path to a class that implements FormInterface. In this case the name of the class is AccountInfoForm. More about the class hierarchy below.

Second, class  AccountInfoForm is defined in its own file.

  modules/custom/optimizely/src/AccountInfoForm.php

Here's an extract from that class definition. To keep it short and to the point, I've omitted parts of the bodies of the methods as well as boilerplate comments.
namespace Drupal\optimizely;
use Drupal\Core\Form\FormBase;
 

class AccountInfoForm extends FormBase {

  public function getFormID() {
    return 'optimizely_account_info';
  }
 

  public function buildForm(array $form, 
                            array &$form_state) {    
    $form['optimizely_id'] = array(      
      '#type' => 'textfield',
      '#title' => t('Optimizely ID Number'),
      '#size' => 60,
      '#maxlength' => 256,
      '#required' => TRUE,
    );
    $form['actions'] = array('#type' => 'actions', );
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => 'Submit',
    );

    return $form;
  }
 

  public function validateForm(array &$form,
                               array &$form_state) {
    if (!preg_match('/^\d+$/', 

          $form_state['values']['optimizely_id'])) {
      \Drupal::formBuilder()->setErrorByName(

        'optimizely_id', $form_state,
        t('Your Optimizely ID should be numeric.'));
    }
  }
 

  public function submitForm(array &$form, 
                             array &$form_state) {
    // Code to update the database ...

    //    . . . . .

    drupal_set_message(t('The default project ...'),
                       'status');    
    // Redirect back to projects listing.
    $form_state['redirect_route']['route_name']
      = 'optimizely.listing';

    return;
  }
}
Notes.
  • Class AccountInfoForm is derived from abstract class FormBase, which implements FormInterface. Because of this hierarchy, class AccountInfoForm must implement methods  getFormIDbuildForm,  and  submitForm. Implementing method validateForm is optional since FormBase contains an implementation (but it is empty, so by default no validating is done). 
  • Whereas in D7 the logic to build a form, validate it, and submit it are provided in three different functions that are related to each other through a naming convention, in D8 the logic is coded into three methods that are part of the same class. The class that you create implements  FormInterface which declares the names and signatures of those methods. By designating the _form  property in the routing file, at runtime D8 core provides for instantiating an instance of your class and for calling the methods at the appropriate times.
  • In buildForm() the render arrays are largely the same as in D7 (from my limited understanding).
  • To set an error message during validation, instead of calling  form_set_error() call \Drupal::formBuilder()->setErrorByName() using the same parameters. Function form_set_error() is deprecated and expected to be removed.

Update for Drupal 8, beta 1: As of beta 1, there have been several API changes that affect the code here and render it partly obsolete. I have written a fresh blog post with code that is correct as of this writing. However, almost all of the notes and non-code content of this post are still accurate and useful.  See http://optimizely-to-drupal-8.blogspot.com/2014/11/implementing-form-d8-beta-1.html

Sources:

Drupal 8: Forms, OOP style, August 12, 2013
http://effulgentsia.drupalgardens.com/content/drupal-8-forms-oop-style
Tutorial on writing forms in Drupal 8 modules. Author has kept code up-to-date with ongoing changes to D8. Very helpful example.

Form API in Drupal 8
https://drupal.org/node/2117411
This is a great reference with examples and explanations.

Form API Reference
https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7
This is from Drupal 7 but seems almost entirely applicable to D8.

interface FormInterface
https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Form!FormInterface.php/interface/FormInterface/8

function  form_set_error
https://api.drupal.org/api/drupal/core!includes!form.inc/function/form_set_error/8
This function is deprecated and should not be use for new code.

May 23, 2014

Implementing multiple tabs on a page

In D7 to provide multiple tabs you can implement hook_menu() and within it, define items with types of MENU_LOCAL_TASK and MENU_DEFAULT_LOCAL_TASK.

To do multiple page tabs in Drupal 8 use its routing system and a separate .yml file instead. In this example, three tabs are created for the Optimizely admin UI.

First, routes need to be defined in the .routing.yml file, one for each tab.

  modules/custom/optimizely/optimizely.routing.yml

File contents:
optimizely.listing:
  path: /admin/config/system/optimizely
  defaults:
    _content: \Drupal\optimizely\Controller\OptimizelyController::helloListing
    _title: Optimizely
  requirements:
    _permission: administer optimizely

optimizely.add_update:
  path: /admin/config/system/optimizely/add_update
  defaults:
    _content: \Drupal\optimizely\Controller\OptimizelyController::helloAddProject
    _title: Optimizely
  requirements:
    _permission: administer optimizely

optimizely.acct_info:
  path: /admin/config/system/optimizely/settings
  defaults:
    _content: \Drupal\optimizely\Controller\OptimizelyController::helloAcctInfo
    _title: Optimizely
  requirements:
    _permission: administer optimizely

Second, there is a .local_tasks.yml file in which the tabs themselves are defined.

  modules/custom/optimizely/optimizely.local_tasks.yml

File contents:
optimizely.listing_tab:
  route_name: optimizely.listing
  title: 'PROJECT LISTING'
  base_route: optimizely.listing
  # route_name == base_route, this tab displays leftmost.

optimizely.add_project_tab:
  route_name: optimizely.add_update
  title: 'ADD PROJECT'
  base_route: optimizely.listing
  weight: 1

optimizely.acct_info_tab:
  route_name: optimizely.acct_info
  title: 'ACCOUNT INFO'
  base_route: optimizely.listing
  weight: 2
Notes.
  • Indentation matters! This is not only for readability but is part of the YAML syntax. Elements that are at the same level must have exactly the same indentation. Tab characters may not be used for indenting.
  • The _title property in the routing file serves as a page title whereas the title property in the local_tasks file serves as a tab label.
  • The base_route property is used to create groupings of tabs. Tabs that have the same value for base_route  belong to the same group.
  • For a particular tab, if  its route_name is the same as the base_route, that tab is the default and is displayed leftmost.
  • For other tabs, the weight property may be used to order them. If there is no weight property, they are displayed in reverse order, i.e. the last one defined in the local_tasks file will render to the left of the others. 
  • YAML has a special syntax that provides block literals so that string values of properties may be split over multiple lines. I tried unsuccessfully to use this in order to make the long paths for the _content properties more readable in this post. Unfortunately, that resulted in unwanted space characters embedded in the paths. That broke the routing process.

Update for Drupal 8, beta 1: The .local_tasks.yml files have been renamed to .links.task.yml. The source article below is up-to-date on this. You can also see http://optimizely-to-drupal-8.blogspot.com/2014/08/mv-localtasksyml-linkstaskyml.html

Update: see  http://optimizely-to-drupal-8.blogspot.com/2014/12/beta-3-beta-4-routes-use-controller.html


Source:

Providing module-defined local tasks
https://drupal.org/node/2122253

May 22, 2014

GitHub repo created for the code

https://github.com/tz-earl/optimizely-to-drupal-8

The contents of this repo will be updated as I progress through converting the Optimizely module to Drupal 8  one part at a time.

hook_permission() is the same as in D7

hook_permission() in Drupal 8 has not changed and can be implemented in the .module file as in D7.
function optimizely_permission() {
  return array(
    'administer optimizely' => array(
      'title' => t('Administer Optimizely module'),
      'description' =>
        t('Administer access to everything in module'),
      'restrict access' => TRUE,
    ),
  );
}
What's different is that the permissions defined in hook_permission() are then used in the  .routing.yml  file to control access to specific paths. For instance,
optimizely.settings:
  path: /admin/config/system/optimizely

  # ...
  requirements:
    _permission: administer optimizely

Update for Drupal 8, beta 1: hook_permission() has been removed. Its functionality is now provided by the .permissions.yml file. See http://optimizely-to-drupal-8.blogspot.com/2014/11/hookpermission-has-disappeared.html


Source:

DRUPAL 8 INFO HOOK TO PLUGIN, DrupalCon Prague, Sept 2013
https://groups.drupal.org/files/pwolanin-2013-09-info-hook-plugin-d.pdf

This presentation states: "Info hooks that simply return a data array - like hook_permission() - without associated functionality - are not candidates to become plugins." I take this to mean that these kinds of hooks will largely remain unchanged.

May 21, 2014

hook_help() is the same as in D7

hook_help() in Drupal 8 has not changed and can be implemented in the .module file exactly as in D7.

The  .module  file is placed at the top-level directory of the module. In our case the location is

    modules/custom/optimizely/optimizely.module

Very abbreviated outline of D7 version, which I copied verbatim to D8:
function optimizely_help($path, $arg) {
  switch ($path) {

    case 'admin/help#optimizely':
      return t('...<a href="@settings">Optimizely...',
        array('@settings' => url('admin/config/system/optimizely/settings')));

    case 'admin/config/system/optimizely':
      return t('... A listing of the Optimizely ...');
    // Other cases ...
  }
}


Update for Drupal 8, alpha 13: hook_help() has a different signature and is no longer unchanged from Drupal 7. See http://optimizely-to-drupal-8.blogspot.com/2014/08/drupal-8-alpha11-alpha13.html


Source:

hook_help()
http://drupalcontrib.org/api/drupal/drupal!core!modules!help!help.api.php/function/hook_help/8

May 20, 2014

Initial cut at routing system with PSR-4 autoloading

According to some docs, Drupal 8 is currently in a transitional phase during which either of the autoloading standards PSR-0 or PSR-4 will work. Regardless, it seems definite that PSR-4 has been accepted and that modules should be implemented to it.

Much of my confusion about the two PSRs was due to not understanding that the difference between them only affects the directory structure and the location of files. Their differences do not affect the conventions for how namespaces are to be defined and used in the code.

Adding to my confusion is the fact that some articles were written to PSR-0, some to PSR-4, and some when it looked like there was going to be a transition from the former to the latter. It took some doing to sort through this.

Following PSR-4, I was able to implement a stripped down, demo use of the routing system.

First, there is the .routing.yml file.

(1)  modules/custom/optimizely/optimizely.routing.yml

(2)  The .routing.yml file replaces the routing functionality that was provided in D7 by implementing hook_menu(). The file contents for this demo are:
optimizely.settings:
  path: /admin/config/system/optimizely
  defaults:
    _content: \Drupal\optimizely\Controller\OptimizelyController::hello
    _title: hello
  requirements:
    _permission: 'view content'
Next, the PHP file that defines the class and its method that are referred to in the .routing.yml file. Each such code file should contain a single class definition. Note the file location.

(3)  modules/custom/optimizely/src/Controller/OptimizelyController.php

(4)  Class definition with its namespace.
namespace Drupal\optimizely\Controller;

class OptimizelyController {
  public static function hello() {
    return array(
      '#markup' => t('<em>Hello Optimizely!</em>'),
    );
  }
}
Notes:
  • The _content property in optimizely.routing.yml specifies a class method to be called to return a render array. The method may be either static or non-static.
  • The namespace Drupal\optimizely corresponds to the directory modules/custom/optimizely/src, where the custom subdirectory is something I added. The remainder of the namespace corresponds one-to-one to subdirectories in the obvious way. In this case, the \Controller portion of the namespace corresponds to the Controller subdirectory, which I added for organizing code files.
  • The #type key which has been documented for the render array is no longer used.
  • A valid D8 module is not required to have a  .module file.
  - - -

These two files together with optimizely.info.yml comprise a working D8 module that displays a "Hello Optimizely!" page when you browse to the path.

Update: see  http://optimizely-to-drupal-8.blogspot.com/2014/12/beta-3-beta-4-routes-use-controller.html


Sources:

Routing functionality of hook_menu() has been replaced by new routing system
https://drupal.org/node/1800686

PSR-4 namespaces and autoloading in Drupal 8
https://drupal.org/node/2156625
A few examples of how PHP namespaces are mapped to the directory structure.

PHP class autoloading changed to PSR-4 instead of PSR-0
https://drupal.org/node/2246699
This article clarified for me that the PSR change only affects file locations, not the PHP namespaces.

Routing system in Drupal 8
https://drupal.org/developing/api/8/routing
Several chapters / webpages on how to use the routing system. I found the first three chapters adequate as a start, as the subsequent ones are more advanced.

May 17, 2014

The module .info file

Here is the original D7 optimizely.info file.
name = Optimizely
description = Manages loading Optimizely generated

    project javascript file(s) to enable A/B testing.
core = 7.x
php = 5.2
project = "optimizely"
configure = admin/config/system/optimizely/settings
files[] = optimizely.test
In Drupal 8 the required .info file for a module has been replaced with a  .info.yml  file, which serves the same purpose. The new syntax is YAML, which is more generally known and accepted.

Here is the very first cut for such a file. These three keys seem to be the minimum required for D8 to recognize the presence of a module.
name: Optimizely
type: module
core: 8.x
Contrib and custom modules may now be placed in the top-level  modules  directory (whereas core modules are placed in  core/modules ). Subdirectories may be used. So I created this new file in the path

modules/custom/optimizely/optimizely.info.yml

Here's a fleshed out version which is equivalent to the original (as far as I know at this point).
# These first three properties are required.
name: Optimizely
type: module
core: 8.x

# This value for the description key is broken across
# multiple lines to demonstrate how it's done and for
# better formatting in this blog.
description: >
  Manages loading Optimizely generated project
  javascript file(s) to enable A/B testing.

php: 5.2
project: optimizely

# The configure property specifies a "route name",
# which I will define later.

configure: optimizely.settings

# files[] are no longer needed because of autoloading
The path to the configuration menu is replaced by use of the new routing system, which I will be learning and getting into later.

From https://drupal.org/node/1935708: "Remove any files[] entries. Classes are now autoloaded using a separate mechanism".

Syntax Notes: 
  • You must put one or more spaces or tabs after each colon.
  • Quote marks are optional around string values.
  • Comments start with a pound sign #
  • Indentation is syntactically significant and indicates elements at the same level.
  • I broke up the value for the description property into two lines by (1) using a > character which means the newlines are to be folded and (2) indenting the lines of the string value.
Sources:

.info files are now .info.yml files
https://drupal.org/node/1935708
Includes a couple of examples on the new .info.yml files.

YAML
https://en.wikipedia.org/wiki/YAML
Description and tutorial for YAML syntax, data types, et al. Has technical details if you want to delve into them but the first part of the article is a good intro.

May 16, 2014

Some beginner's tutorials on converting

I went through some beginner's tutorials on converting Drupal 7 modules to Drupal 8. The three below all included good introductions for the first few steps. My focus was to get an overall sense of the changes and differences that one would need to grapple with at the onset.

One thing to keep in mind is that when these tutorials were written, they were dealing with a moving target. Some of the details, such as the name of a property, have changed. Reader beware!

  - - - - -
Drupal 8 Developer Prep
http://buildamodule.com/collection/drupal-8-developer-prep

Video tutorials to get started with a simple D8 module. Good comparisons with how things are done in D7.

There is a written transcription along the side that is highlighted along with the audio, which is great for people like me who learn better by reading rather than listening. The transcription also makes it easy to scroll back and re-read the material or read something that I missed in the audio while I was thinking for a few seconds about something else. I watched the first few videos out of several.

  - - - - -
Drupal 8: Writing a Hello World Module, 7-30-2013
http://drupalize.me/blog/201307/drupal-8-writing-hello-world-module

Twenty-minute video tutorial with some textual notes. It's out of date with respect to the PSR standard and a few other details but otherwise good.

  - - - - -
Drupal 8: Hello OOP, Hello world!,  8-02-2013
http://effulgentsia.drupalgardens.com/content/drupal-8-hello-oop-hello-world

This is a good elaboration of the previous article and provides great code samples and explanation.

May 15, 2014

Development environment, etc.

I'm in the process of moving away from Windows as my main development platform and migrating to Linux. At the moment, I've got a composite environment consisting of a Windows host with Linux Mint running in a virtual machine.

The virtualization is provided by VirtualBox 4.3. It's great because directories in the host can be made accessible to and mounted by the guest. Moreover, the clipboard can be shared and enabled to be bidirectional.

However, for performance reasons, I'm starting with the following stack since things run a bit sluggish in the vm on my system. For source code editing, I intend to use Sublime Text 2 under Linux.

Windows 7
Apache  2.2
MySQL  5.6
PHP  5.4

Optimizely module:  7.x-2.14
Drupal 8 core:  8.0-alpha11
Drupal 7 core:  7.28

Installing Drupal 8 was very routine and similar to installing 7. But when you log in, you may get scary sounding messages highlighted in red about your version of Drupal no longer being supported and that you should update. Those messages are spurious. This is what you get for being on the bleeding edge. The "Modules" menu item is now called "Extend"; otherwise, the admin menu looks pretty familiar if you've been around the block a few times with D7.

drush 5.8 (for Windows) is not working with D8. There is mention of drush 7 for Drupal 8 for Linux, here: http://drupal.reea.net/installing-drush-7-drupal-8

New to it all

On the suggestion of Darren "DeeZone" Lee at DoSomething.org I'm starting this blog to record my experiences in converting the Drupal 7 Optimizely module to Drupal 8. Dee is the main committer to the module and is the person who has pushed it to its current state of functionality.

As for myself, it's all new to me: Drupal 7, Drupal 8, Optimizely, and many of the tools and technologies currently in vogue. What hasn't changed about web/software development is the need to be able to think precisely in an algorithmic manner. The ability to do that was burned into my synapses a couple of lifetimes ago when I worked in software companies.

Anyway, I come into this as a novice, so I will be bringing the eyes and the perspective of someone who is coming in fresh. Mostly, I will focus on the gory details of getting things to work as I do this conversion.