Upgrade your module to d8

Based on CAPTCHA module experience

Artem Miroshnyk

Web developer

Propeople Ukraine

Agenda

  • You HAVE to start upgrading your modules now!
  • Module upgrade checklist
  • Tools, manuals, community that will help you
  • CAPTCHA
    • Problems of 7.x version
    • 3 ways to make it works
    • How can you help
  • Conclusion

Start upgrading your modules now!

Why?

Drupal 8.0.0-beta2 has been released

  • Real code freeze
  • No critical bugs
  • No big documentation changes
  • Release candidate is close enough!

It's still beta :)

And you have a chance to modify something in core if it fits your module and will bring benefits to others

D8 stable will be released

Sooner or later ;)

And you have to be ready to start use it

PHP OOP, OOD, Symfony 2, PSR4, Composer, new D8 APIs, Twig, etc

And videos, blogposts and sessions won't help you as much as coding

Upgrade checklist

Just put your module into drupal 8 project with all dependencies

Any IDE or configured Text Editor will show most of broken places:

  • Removed functions
  • Modified functions
  • Deprecated functions

Other wrong places you will notice from runtime errors/notices.

Left parts of your code could be changed into completely new system.

Deprecated

if (!empty($views_field_options['fieldset']['id'])) {
    $id = $views_field_options['fieldset']['id'];
    $id = $views_field->handler->tokenize_value($id, $view->row_index);
    $vars['attributes_array']['id'] = drupal_html_id($id);
  }

Most IDEs will highlight deprecated function.

Also all deprecated function/classes/methods in D8 has comments when it will be removed and what to use instead.

/**
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.0.
 *   Use \Drupal\Component\Utility\Html::getUniqueId()
 */
function drupal_html_id($id) {
  return Html::getUniqueId($id);
}

Removed

Fatal error: Call to undefined function Drupal\image_captcha\Plugin\Captcha\watchdog() in 
/var/www/modules/captcha/image_captcha/src/Plugin/Captcha/ImageCaptcha.php on line 35

Stunning error... But thanks Drupal team we have "Change records for Drupal core" that has record for our problem - "hook_watchdog() and watchdog() removed" and some other useful stuff if you will try to search using "watchdog" keyword.

No D8 procedural code looks like that:

<?php
// Logs a notice
\Drupal::logger('my_module')->notice($message);
// Logs an error
\Drupal::logger('my_module')->error($message);
?>

CMI

Hook_menu()

YML, HTTP Foundation, HTTPKernel, Routing, Controllers

New stuff

Entity&Field API

// Forget about:
$node->body[LANGUAGE_NONE][0]['value']
$node->title
$tid = $node->field_tags[LANGUAGE_NONE][2]['tid']
$term = taxonomy_term_load($tid);
$term->name

// Meet new D8!
$node->body->value
$node->title->value
$node->field_tags[2]->name

Plugins API

/**
 * Class ImageCaptcha
 * @package Drupal\image_captcha\Plugin\Captcha
 *
 * @Captcha(
 *   id = "image",
 *   title = @Translation("Image captcha")
 * )
 */
class ImageCaptcha extends PluginBase implements CaptchaInterface, ContainerFactoryPluginInterface {

D8 requires lots of new knowledge and skill

How could we handle it?

Drupal is famous by its magic

"Do you need X feature? Drupal just has module X for this feature!"

The same magic with similar mages are available in other local communities: Symfony, Composer, Assets, Zend, etc

Let's become friends!

Composer

  • PHP dependency manager (Now we have own Bundler, Bower, etc)
  • Autoloader manager (composer install and class will be autoloaded)
  • Packagist.org database of php classes!
drush dmu-analyze MODULE_NAME

This will print a report showing any relevant change notices where you can read more.

The script will output a few lines as it attempts various conversions. Go into your modules/MODULE_NAME directory and check out all of your new YAML files and such. 

drush dmu-upgrade MODULE_NAME

Continues Integration

Tests

PHP_CodeSniffer

PHP Mess Detector

PHP Copy/Paste Detector

GitHub

TravisCI

CAPTCHA

required module for public site

interface for such submodules like recaptcha, keycaptcha, draggable captcha

But 7.x version has probems

  • Dirty code (code sniffer returns thousand of warnings!)
  • Image captcha has handmade CAPTCHA generation without performance, unit and functional tests
  • CAPTCHA completely disables page cache
  • Has too much settings which are hard to understand
  • Has weird UI that is done via too much custom code
  • HoneyPot and Mollom are much better modules, lets fix it!

What is going outside of drupal?

  • neutron/recaptcha - The most used implementation of reCAPTCHA and compatible with HTTP Foundation

  • gregwar/captcha - The most used stadalone image captcha library also compatible with HTTP Foundation

Step 1

 Capcha settings for particular form transform into custom entity

/**
 * Defines the CaptchaPoint entity.
 *
 * @ConfigEntityType(
 *   id = "captcha_point",
 *   label = @Translation("Captcha Point"),
 *   handlers = {
 *     "list_builder" = "Drupal\captcha\CaptchaPointListBuilder",
 *     "form" = {
 *       "add" = "Drupal\captcha\Form\CaptchaPointForm",
 *       "edit" = "Drupal\captcha\Form\CaptchaPointForm",
 *       "delete" = "Drupal\captcha\Form\CaptchaPointDeleteForm"
 *     }
 *   },
 *   fieldable = FALSE,
 *   config_prefix = "captcha_point",
 *   admin_permission = "administer CAPTCHA settings",
 *   entity_keys = {
 *     "id" = "formId",
 *     "label" = "label",
 *   },
 *   links = {
 *     "edit-form" = "captcha_point.edit",
 *     "delete-form" = "captcha_point.delete",
 *   }
 * )
 */
class CaptchaPoint extends ConfigEntityBase implements CaptchaPointInterface {

Step 2

Implement Captcha plugin type instead of hook_captcha_info()

/**
 * MathCaptcha class.
 *
 * @Captcha(
 *   id = "math",
 *   title = @Translation("Math Captcha")
 * )
 */
class MathCaptcha extends PluginBase implements CaptchaInterface {

  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->firstAddendum = mt_rand(1, 20);
    $this->solution = mt_rand(1, $this->firstAddendum);
    $this->secondAddendum = $this->solution + $this->firstAddendum;
  }

  public function getQuestionFormElement(array $form, FormStateInterface $form_state, $captcha_sid) {
    return array(
      '#markup' => $this->firstAddendum . ' + ' . $this->secondAddendum
    );
  }

  public function getAnswerFormElement(array $form, FormStateInterface $form_state) {
    return array(
      '#type' => 'textfield',
      '#title' => t('Math question'),
      '#description' => t('Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.'),
      '#field_prefix' => t('@x + @y = ', array('@x' => $this->firstAddendum, '@y' => $this->secondAddendum)),
      '#size' => 4,
      '#maxlength' => 2,
      '#required' => TRUE,
      '#attributes' => array(
        'autocomplete' => 'off',
      ),
    );
  }

  public function getChallengeDescription() {
    return $this->t('Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.');
  }

  public function getQuestionDescription() {
    return $this->t('Math');
  }

  public function getSolutionValue($captcha_sid) {
    return $this->solution;
  }

}

Step 3

rewrite image captcha generation using external captcha library

services:
 controller.image_captcha:
  class: Drupal\image_captcha\Controller\CaptchaImageGenerator

 image_captcha.generator:
   class: Drupal\image_captcha\Generator\ImageCaptchaGenerator
   arguments:
     - @gregwar_captcha.captcha_builder
     - @gregwar_captcha.phrase_builder

 gregwar_captcha.captcha_builder:
   class: Gregwar\Captcha\CaptchaBuilder

 gregwar_captcha.phrase_builder:
   class: Gregwar\Captcha\PhraseBuilder

Step 4

Provide continues ingration based on Travis ci

PHPUnit, WebTests, Code Styles, Drupal Build

  - composer global require drush/drush:dev-master
  - composer global require youngj/httpserver:dev-master
  - composer global require squizlabs/php_codesniffer:$PHPCS_VERSION
  - composer global require drupal/coder:$CODER_VERSION
  - sudo apt-get update
  - sudo apt-get install apache2 libapache2-mod-fastcgi
  - sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
  - sudo a2enmod rewrite actions fastcgi alias
  - echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
  - ~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm
  - echo "$(curl -fsSL https://gist.githubusercontent.com/nickveenhof/11386315/raw/b8abaf9304fe12b5cc7752d39c29c1edae8ac2e6/gistfile1.txt)" | sed -e "s,PATH,$TRAVIS_BUILD_DIR/../drupal,g" | sudo tee /etc/apache2/sites-available/default > /dev/null
  - sudo service apache2 restart
  - curl -v "http://localhost"
before_script:
  - ln -s ~/.composer/vendor/drupal/coder/coder_sniffer/Drupal ~/.composer/vendor/squizlabs/php_codesniffer/CodeSniffer/Standards

  - TESTDIR=$(pwd)
  - cd ..
  - mysql -e 'create database drupal'
  - git clone --depth 1 --branch $DRUPAL_VERSION $DRUPAL_REPO drupal
  - cd drupal
  - php -d sendmail_path=`which true` ~/.composer/vendor/bin/drush.php --yes site-install --db-url=mysql://root:@127.0.0.1/drupal testing
  - composer require gregwar/captcha:1.0.11
  - ln -s $TESTDIR modules/$MODULE_NAME
  - drush --yes pm-enable simpletest $MODULE_NAME

script:
  - phpcs --report=full --standard=Drupal ./modules/$MODULE_NAME
  - php ./core/scripts/run-tests.sh --php `which php` --concurrency 12 --url http://localhost/ --verbose --color "$MODULE_TEST_GROUP"
  - ./core/vendor/phpunit/phpunit/phpunit -c ./core/phpunit.xml.dist ./modules/$MODULE_NAME

github.com/M1r1k/captcha

Help is approciated

Port your module on D8. E.g. lets port CAPTCHA module.

By Artyom Miroshnik

Port your module on D8. E.g. lets port CAPTCHA module.

  • 1,640