Jun 27, 2014

Function drupal_lookup_path() has been removed

In one place in the Optimizely code, I replaced the function as follows, where the first parameter to drupal_lookup_path() is 'alias'.

Drupal 7:
$path .= drupal_lookup_path('alias', $path);
Drupal 8:
$alias =
  \Drupal::service('path.alias_manager')
    ->getPathAlias($path);

if (strcmp($alias, $path) == 0) {
  $alias = '';  // No alias was found.
}

$path .= $alias;

If no alias is found, getPathAlias() returns its argument, hence the string comparison to check whether an alias was found or not.

In one of the new classes AddUpdateForm of the converted module, there was code copied over that contained numerous calls to drupal_lookup_path().

I decided to write two adapter methods to interface with the new alias manager. Those methods pass back return values that are identical to those of drupal_lookup_path() so that the methods can be easily dropped into the existing code that calls the old function.
private function lookupPathAlias($path) {
  $alias =
    \Drupal::service('path.alias_manager')
      ->getPathAlias($path);

  return (strcmp($alias, $path) == 0) ?
            FALSE : $alias;
}

private function lookupSystemPath($alias) {
  $path =
    \Drupal::service('path.alias_manager')
      ->getSystemPath($alias);

  return (strcmp($path, $alias) == 0) ?
            FALSE : $path;
}

With these two methods, the following, for example,
if (drupal_lookup_path('alias', $path) === FALSE &&
    drupal_lookup_path('source', $path) === FALSE) {
  return $path;

}
is simply replaced with
if ($this->lookupPathAlias($path) === FALSE &&
    $this->lookupSystemPath($path) === FALSE) {
  return $path;
}

Update for Drupal 8, alpha 13: method names for class AliasManager have changed. See  http://optimizely-to-drupal-8.blogspot.com/2014/08/drupal-8-alpha11-alpha13.html

Sources:

function drupal_lookup_path
https://api.drupal.org/api/drupal/includes!path.inc/function/drupal_lookup_path/7

Path alias management is now pluggable
https://www.drupal.org/node/1853148

Jun 24, 2014

Function check_plain() deprecated

Use String::checkPlain() instead.

For example,
use Drupal\Component\Utility\String;
$addupdate_form['optimizely_project_code'] = array(
  '#type' => 'textfield',
  '#default_value' => String::checkPlain($project_code),
  // Other fields ...
);
Update:  See the later post  Drupal 8, Beta 15 --> RC 1: Eliminating use of checkPlain().

Sources:

Replace calls to check_plain() with Drupal\Component\Utility\String::checkPlain()
https://www.drupal.org/node/2089331

30 Awesome Drupal 8 API Functions You Should Already Know, Fredric Mitchell
http://brightplumbox.com/d8apifunc-austin/#/4/1

Jun 22, 2014

Functions variable_set() and variable_get() are removed

The Optimizely module for D7 uses variable_set() and variable_get() in a number of places for storing and accessing the Optimizely account id number that is used to access the Optimizely service.Those two functions have been removed from Drupal 8.

After considering some of the possible options to replace that code, I decided that the Simple Configuration API would be a good fit since we only need to store and retrieve a single persistent value.

In this API, there are named groups of values. The name of each grouping consists of the module name plus a "subsystem" name, for example, 'optimizely.settings'. Within each group, there are key-value pairs. In our case, there is a single key, optimizely_id, whose value is a positive number.

Accordingly, I wrote a class AccountId to encapsulate the API. The class definition is below and should be mostly self-explanatory.

An object of core class Config corresponds to a named group of values. Once you instantiate an object of this type by calling \Drupal::config(), you can then call its set() and get() methods for specific key-value pairs.

namespace Drupal\optimizely;

class AccountId {

  private static $config = NULL;

  private static function getConfig() {
    if (! self::$config) {
      self::$config = \Drupal::config('optimizely.settings');
    }
    return self::$config;
  }

  public static function getId() {
    $config = self::getConfig();
    $optimizely_id = $config->get('optimizely_id');
    return $optimizely_id;
  }

  public static function setId($id) {
    $config = self::getConfig();
    $config->set('optimizely_id', $id);
    $config->save();
    return TRUE;
  }

  public static function deleteId() {
    // N.B. This deletes any and all settings
    // stored in "optimizely.settings".

    $config = self::getConfig();
    $config->delete();
    return TRUE;
  }
}


This class is then used, for example, in AccountSettingsForm::buildForm() to set the default value for the Optimizely id form field,
'#default_value' => AccountId::getId(),
and then in AccountSettingsForm::submitForm() to save the new id,
$optimizely_id = $form_state['values']['optimizely_id'];
AccountId::setId($optimizely_id);
  - - - - -
As originally implemented, each group of values is stored in a YAML file. Such files are still used for import and export purposes and for synchronizing configuration across different Drupal sites.

However, during normal usage, the API storage is only in the database, with no YAML files needed. In the database, table config contains all the groups of values. The name column has the group names. The corresponding data column contains all the individual values for that group.

  - - - - -
Besides the Optimizely account id, I encountered one other use of variable_get().
$front_path = variable_get('site_frontpage');
After searching for the string 'site_frontpage' in the core code for both D7 and D8, examining the search results, and doing a bit of testing, I replaced the above line of code with
$config = \Drupal::config('system.site');
$front_path = $config->get('page.front');
  - - - - -

Update: See  Beta 6: ConfigFactoryInterface::getEditable()
http://optimizely-to-drupal-8.blogspot.com/2015/02/beta-6-configfactoryinterfacegeteditable.html

Sources:

Overview of Configuration (vs. other types of information)
 https://drupal.org/node/2120523

Simple Configuration API
https://drupal.org/node/1809490
Very useful for concepts and actual examples of code.

Configuration Storage in Drupal 8
https://drupal.org/node/2120571
Configuration file format, file location, and use of YAML files.

Jun 19, 2014

Function drupal_goto() has been removed


Function drupal_goto() is gone and must be replaced with the use of something else.

For example, in  AddUpdateForm::submitForm(), which is a method implemented in a class derived from FormBase, I changed
drupal_goto('admin/config/system/optimizely');
to
$form_state['redirect_route']['route_name'] =
  'optimizely.listing';
where optimizely.listing is a route defined in the routing file and $form_state is a parameter passed in by reference.

The source article below suggests the use of  RedirectResponse() instead of  drupal_goto(). However, I haven't been able to figure out how to use it in submitForm(), and I've yet to find examples that are at my level of understanding.

Finally, here's a quote from a relevant comment made by Crell, a.k.a. Larry Garfield:
"For instance, if you're using the common controller base class there is a $this->redirect($route_name) method ready for you; ... "
That looks very straightforward, but the required context is being in an instance of the common controller base class.

Update:  redirection can no longer be done as above. See the note on method setRedirect() in this newer post  http://optimizely-to-drupal-8.blogspot.com/2014/08/drupal-8-alpha-13-alpha-14.html#setredirect

Source:

drupal_goto() has been removed
https://drupal.org/node/2023537

Jun 17, 2014

db_*() functions largely remain the same, at least in basic usage

Functions for accessing and manipulating the database are as in D7. So far, I have successfully used the following functions in Drupal 8 while changing very little.

db_select() 
db_query() 
db_update()
db_insert()

I did run into one problem with using db_query() to carry out a SELECT statement. Applying the rowCount() method to the object returned by db_query() resulted in this runtime error.
Drupal\Core\Database\RowCountException: rowCount() is supported for DELETE, INSERT, or UPDATE statements performed with structured query builders only,  . . .
I'm not sure what that message means. I ended up replacing this offending code (I omitted most of the SQL statement)
$query = db_query('SELECT project_title' . 
                   ' FROM {optimizely} WHERE .... ');
$query_count = $query->rowCount();
with
$query = db_query('SELECT project_title' . 
                   ' FROM {optimizely} WHERE .... ');
$results = $query->fetchCol(0);
$query_count = count($results);
If you're doing anything a bit advanced, you may run into other problems. One such use case is described in the source article below.

Sources:

Paging db_selects in Drupal 8
http://running-on-drupal8.co.uk/blog/paging-dbselects-drupal8

public function Statement::rowCount
https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Database!Statement.php/function/Statement%3A%3ArowCount/8

Jun 15, 2014

Take two: Building a form using a route path with a wildcard


This posting is a follow-up to the one published immediately before this one. It's necessary to read that older post first in order to understand this one, which provides an alternate and simpler solution.

http://optimizely-to-drupal-8.blogspot.com/2014/06/building-form-using-route-path-with.html

   - - - - -
Thanks to Darren Lee who pointed out to me another way to pass an optional parameter to a form building class derived from FormBase, I was able to implement an alternate solution that is simpler and cleaner.

This solution does not require a class variable or the constructor that assigns a value to it. Instead, the buildForm() method receives an extra optional parameter instead of using a class variable.


(1)  First, as in the previous post, it's necessary to define a route in the routing file, which is the same as before. The path contains the wildcard {oid}. DoUpdate is a new class to be defined and acts as an intermediary.

optimizely.add_update.oid:
  path: /admin/config/system/optimizely/add_update/{oid}
  defaults: 

    _content: \Drupal\optimizely\DoUpdate::buildUpdateForm
    _title: Optimizely Edit Project
  requirements:
    _permission: administer optimizely



(2)  The definition for class  DoUpdate simply consists of the method buildUpdateForm() specified in the route above. By using the  _content  property, the value of {oid} in the path is automatically passed as an argument to the method.

In the body of buildUpdateForm(), the first parameter in the call to  getForm() is the name of the form building class instead of an instance of that class, which is what was passed before. In addition, there is a second parameter, which is the value of the wildcard.

class DoUpdate {
  public static function buildUpdateForm($oid) {
    return \Drupal::formBuilder()->getForm(

         'Drupal\optimizely\AddUpdateForm', 
         $oid
      );
  }
}



(3) Finally, the buildForm() method of the form building class has an optional parameter $oid. When this method is called for the wildcard path defined in the routing file, the param will have a value set. Otherwise, the param defaults to NULL.

class AddUpdateForm extends FormBase {

  public function buildForm(array $form, array &$form_state, 
                            $oid = NULL) {
    // body of buildForm ...
  }
  // ... the rest of the class definition, as before.
}

If the value of $oid is set, the  buildForm() method uses it to fetch the values of the existing project from the database to pre-populate the fields of the form that it builds. Otherwise, the fields are left blank.

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

Source:

Form API in Drupal 8
https://drupal.org/node/2117411

Jun 13, 2014

Building a form using a route path with a wildcard

The Optimizely module provides a combined Add/Update form that is used both to add a new project as well as edit an existing one.

In D7 this is implemented in  hook_menu() by defining two different paths for bringing up the form. One path is for the "add" version with the fields blank. The other path includes a positive integer that is a unique id for a project. The project id is used to prepopulate the fields. Note the wildcard % in the second path below.

$items['admin/config/system/optimizely/add_update'] = array(
  'title' => 'Add Project',
  'page callback' => 'drupal_get_form',
  'page arguments' => array('optimizely_add_update_form'),

  'type' => MENU_LOCAL_TASK,
  // ...
);

$items['admin/config/system/optimizely/add_update/%'] = array(
  'title' => 'Edit Project',
  'page callback' => 'optimizely_add_update_update',
  'page arguments' => array(5),

  // ...
);

For the "edit" version, function   optimizely_add_update_update() acts as an intermediary to accept the project id as an argument and pass it on as an optional parameter to function  optimizely_add_update_form(), which does the actual form building.

function optimizely_add_update_update($target_project) { 
  return drupal_get_form('optimizely_add_update_form', 

                         $target_project);
}


For conversion purposes, I wanted to keep this path structure intact. This turned out to be a little harder to do in Drupal 8 than in D7.

First, as usual, it's necessary to define a route in the routing file. My initial attempt was to do the following, using the AddUpdateForm class that I had previously created for the "add" form.

optimizely.add_update.oid:
  path: /admin/config/system/optimizely/add_update/{oid}
  defaults: 

    _form: \Drupal\optimizely\AddUpdateForm
    _title: Optimizely Edit Project
  requirements:
    _permission: administer optimizely


My thinking was that there would be some way to pass the value of the {oid} wildcard to the AddUpdateForm::buildForm() method. Unfortunately, I remained stymied in finding a way to add such an optional parameter.

Eventually, I decided to use an intermediary class and method, changing the route definition as follows.

optimizely.add_update.oid:
  path: /admin/config/system/optimizely/add_update/{oid}
  defaults: 

    _content: \Drupal\optimizely\DoUpdate::buildUpdateForm
    _title: Optimizely Edit Project
  requirements:
    _permission: administer optimizely


The key difference is the use of the  _content  property rather than  _form. The class definition is simply

class DoUpdate {
  public static function buildUpdateForm($oid) {
    return \Drupal::formBuilder()->getForm(new AddUpdateForm($oid));
  }
}


By using the  _content  property there was no problem in passing the value of {oid} to the specified method, which is done automatically. That value is then passed along as a parameter to a constructor for class AddUpdateForm, which also has a new class variable in addition to the constructor.

class AddUpdateForm extends FormBase {

  // A positive integer which is a unique Optimizely project id.
  private $oid = NULL;

  public function __construct($oid = NULL) {
    $this->oid = $oid;
  }


  // ... the rest of the class definition, as before.
}

The  buildForm() method then uses the value of the class variable to fetch the existing values of the project from the database.

The final piece was to add the wildcard path to hook_help() so that the same help message could be displayed on the edit page. This also turned out to be a little tricky.

My first attempt was to do the following in the switch statement comprising the body of the function, adding the second case to the same help text.

case 'admin/config/system/optimizely/add_update':
case 'admin/config/system/optimizely/add_update/%':
  return t('Add or edit specific project entries. ...');


That wildcard did not work. The path that was passed to  hook_help()  had the actual project id, not a wildcard. So at the beginning of  hook_menu I inserted this code to fake the wildcard.

function optimizely_help($path, $arg) {

  // Look for paths for updating particular projects and
  // fix them to match in the switch statement below.
  // The replacement string includes % as a kind of wildcard.


  $pattern = ':^admin/config/system/optimizely/add_update/\d+$:';
  $replacement = 'admin/config/system/optimizely/add_update/%';
  $path = preg_replace($pattern, $replacement, $path);



Update: see this newer post for a better solution  http://optimizely-to-drupal-8.blogspot.com/2014/06/take-two-building-form-using-route-path.html

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



Sources.

Drupal 8 using Drupal::formbuilder()->getForm();
http://domainpiranha.com/Drupal-8-formbuilder-reusing-forms

Parameter upcasting in routes
https://drupal.org/node/2122223

Jun 11, 2014

Which t() function to call


In Drupal 8 calling the global t() function within a class context seems strongly deprecated, although it still works. Instead, use
$this->t()
My understanding of the rationale is that calling the class method provides better encapsulation and testability, and it also allows for the possibility of invoking a different version of t() other than whatever global default there is.

In non-class contexts, such as within functions defined in the .install script, use the global t() as in D7.

The following article seems to imply that the use of  $this->t() is either required or a really good idea if you want to enable unit testing. The article is mainly about how D8 core should implement  t() as a class method and has an extremely long thread of comments by core maintainers.

Provide a trait for $this->t() and $this->formatPlural()
https://drupal.org/node/2079797

Jun 10, 2014

Better use of the t() function for translators

When coding the use of the t() function to indicate strings that may need to be translated, it's much better to prepare and pass a complete message rather than fragments of a message.

As pointed out by the source articles below, one compelling reason for doing so is that a single string that contains a complete message provides context that might otherwise be missing. Context is extremely important in understanding the words and phrases that are used so that both an accurate and a syntactically correct translation might be provided.

For example,
drupal_set_message(t('Enter your ' .
    l(t('account number'), '/mymodule/settings') .
    t('. There are also the ') .
    l(t('permissions'), '/permissions/module-mymodule') .
    t(' to set for specific roles.')),
  'status');
As I understand it, the above code would result in five different strings to be translated that the translator might not readily know are really part of a single message.

1)  '. There are also the '
2)  ' to set for specific roles.'
3)  'Enter your '
4)  'permissions'
5)  'account number'

A second reason that the above code is problematic is that it assumes that the word order of the target languages is the same as in English. Not so! For instance, in Korean, the main verb of a sentence is normally at the very end. In Swahili, adjectives and other noun modifiers almost always come after the noun, not before. The five strings that are concatenated above are in a fixed order that cannot be changed except by modifying the code.

Here's a different way to code this to enable better translations.
drupal_set_message(t('Enter your ' .
    '<a href="@url_1">account number</a>.' .
    ' There are also the ' .
    '<a href="@url_2">permissions</a>' .
    ' to set for specific roles.',
    array('@url_1' => url('/mymodule/settings'),
          '@url_2' 

              => url('/permissions/module-mymodule'))),
  'status');
There is a single string and a single call to the t() function. The translator can change word order in any way appropriate, including the two HTML tags, whose targets are generated.

The downside is that the translator must be able to handle a bit of HTML since the string they will see is:
Enter your <a href="@url_1">account number</a>. There are also the <a href="@url_2">permissions</a> to set for specific roles.
See the sources below for more examples and discussion.

Update for Drupal 8, beta 1: function url() has been replaced. See http://optimizely-to-drupal-8.blogspot.com/2014/11/beta-1-released-more-changes.html

Sources:

function  t
https://api.drupal.org/api/drupal/includes!bootstrap.inc/function/t/7

function  l
https://api.drupal.org/api/drupal/includes!common.inc/function/l/7

Dynamic strings with placeholders
https://drupal.org/node/322732

Jun 9, 2014

Issue: Uninstalling a module in the presence of a shortcut for it

When I started working on the .install script, I first tried to uninstall the module. This completely broke the site and made it unusable. Only a page with an error message would be displayed no matter what path I tried. The message stated that a RouteNotFoundException was thrown for route optimizely.add_update.

The full error message is:
Symfony\Component\Routing\Exception\RouteNotFoundException:
Route "optimizely.add_update" does not exist.
in Drupal\Core\Routing\RouteProvider->getRouteByName()
(line 150 of core\lib\Drupal\Core\Routing\RouteProvider.php). 
Searching through the database, I found a row in table shortcut that referred to that route. After deleting the row, the site worked fine.

I don't remember creating such a shortcut - I didn't even know about shortcuts before this problem happened - and don't know how it came into being.

But I was able to reproduce this by manually adding a shortcut for the path to one of the Optimizely tabs, then uninstalling the module. The same error message came up.

I haven't found any recourse other than to go into the database to delete the shortcut from the shortcut table.

The first article below reports this issue along with a recent attempt to fix this in core.

Sources:

RouteNotFoundException when a module (previously added to shortcuts) disabled
https://drupal.org/node/2266325

Working with the shortcut bar
https://drupal.org/documentation/modules/shortcut




Jun 8, 2014

The .install script: hook_schema(), hook_install(), hook_enable(), etc.

Converting the optimizely.install script:

hook_schema() works as is under Drupal 8 without any changes.

hook_install() works as is except that the  st() function which is used within installation scripts has been removed in Drupal 8. The  t() function is used instead.

In D7 you could disable a module without uninstalling it, but with Drupal 8 you can only uninstall. The source articles below describe numerous problems that can happen when a module is disabled but not uninstalled, so this is changed for D8. A module is either installed or not installed.

Consequently, hook_enable() and hook_disable() have been removed. Functionality that was previously in hook_enable() may need to be moved to hook_install(). Functionality that was previously in hook_disable() may need to be moved to hook_uninstall().

Upon uninstalling, hook_uninstall() automatically removes tables defined in hook_schema() as in D7.

I removed the existing several implementations of  hook_update_N(), for the moment just creating optimizely_hook_8000(). The function only sets a status message since the schema is not changing during this conversion.


Sources:

Modules cannot be in a disabled state anymore, only installed and uninstalled
https://drupal.org/node/2193013

Disabled modules are broken beyond repair so the "disable" functionality needs to be removed
https://drupal.org/node/1199946

Removed st() and get_t(), just use t() in place, simple!
https://drupal.org/node/2021435


Jun 4, 2014

Theme functions are gone, more or less

For the Project Listing tab of the module, the D7 version defines an item in  hook_menu()  that names function  optimizely_project_list_form()  to be called to create the form.

(1)  That function first queries the database to collect the raw data for existing projects.

(2)  Through the #theme property, the function specifies use of a theme hook that is declared in hook_theme(). The theme hook is implemented by a theme function, which builds most of the render array.

(3)  The theme function makes an explicit call to the core theme() function to do the actual rendering, passing table as the first param for that call.

Because there are indications that in Drupal 8 the use of theme functions is strongly discouraged, calling  theme() directly is discouraged, and because the D7 implementation seemed unnecessarily complex, I studied the code to see if I could come up with an alternative.

The upshot is that I was able to refactor the code to avoid what is deprecated and to make the call structure a little simpler.

Keep in mind that for implementing forms, we are now dealing with a class derived from FormBase.

(1)  The buildForm() method first accesses the database to collect raw data, exactly as before.

(2)  For the #theme property of the render array, the method provides a value of table. No theme hook is used, nor is there a call to theme().

(3)  The gut logic that was previously in the theme function was extracted into a private method of the same class that implements the form. This new method is directly called once for each project, i.e. each row of the table, to provide its addition to the render array.

One notable thing about this conversion task is that I was able to copy-paste large chunks of code as is. It was only the overall flow that was changed. The contents and the details of the resulting render array were almost exactly the same, as were almost all of the control structures.

Sources:

Completely new theme/template system: Twig
https://drupal.org/node/1831138

Jun 2, 2014

Twig template with "if" conditional

The next step I took was to implement the use of a D7 template file that contains conditional markup wrapped by PHP, converting it to Twig. The original code looked something like the following. I have removed most of the markup, keeping just enough to illustrate the point.
<p>The basic configuration and design ... </p>

<?php if (($variables['form']['optimizely_project_code']['#default_value'] == 0) && ($variables['form']['optimizely_oid']['#value'] == 1)): ?>

  <p>In order to use this module, ... </p> 
  <p>The default Project ...  </p>


<?php endif; ?>

<?php echo drupal_render_children($form)

Here's a version for Twig.
<p>The basic configuration and design ... </p>

{% if form['optimizely_project_code']['#default_value'] == 0 and form['optimizely_oid']['#value'] == 1 %}

  <p>In order to use this module, ... </p> 
  <p>The default Project ...  </p>


{% endif %}

{{ form }}

As in the previous post, {{  }} is used to print the expression that is enclosed, which may be a variable name, a string literal, etc.

There is an alternate syntax for array expressions that is more compact and a little easier to read. The following two lines are equivalent.
form['optimizely_oid']['#value']
form.optimizely_oid['#value'] 
But the next line results in a syntax error because of the pound sign # so you can't always use this dot notation.
form.optimizely_oid.#value

Note:  <?php  ?>  delimiters and their enclosed PHP code are ignored in Twig templates.

Sources:

TWIG > tags > if
http://twig.sensiolabs.org/doc/tags/if.html

Creating and Using Templates
http://symfony.com/doc/current/book/templating.html