Writing code, both science and art

Jan Zavrl

Drupal Camp Zagreb, May 2017

Jan Zavrl

@jnzavrl | jzavrl | janzavrl.me

@ndp_studio | ndp-studio.com

Writing code, both science and art, Zagreb, May 2017​

The what and the why

  • Rules, expectations, consistency
  • Two types - style and substance
  • Automating API documentation
  • For the guy next to you and after you

Writing code, both science and art, Zagreb, May 2017​

Let's start with the basics

  • Code style and formatting
  • Indentation
  • Spaces
  • Opening tags, end with blank line
  • Line wrapping and length

Writing code, both science and art, Zagreb, May 2017​

Writing code, both science and art, Zagreb, May 2017​

if (isset($view_modes['teaser'])) {
  entity_get_display('node', $type->id(), 'teaser')
    ->setComponent('body', array(
      'label' => 'hidden',
      'type' => 'text_summary_or_trimmed',
    ))
    ->save();
}
if(isset($view_modes['teaser'])){
  entity_get_display('node',$type->id(),'teaser')
    ->setComponent('body',array(
      'label'=>'hidden',
      'type'=>'text_summary_or_trimmed'
    ))->save();
}

Control structures

  • If, switch and try statements
  • Template exceptions

Writing code, both science and art, Zagreb, May 2017​

Writing code, both science and art, Zagreb, May 2017​

if ($actual_count == $expected_count) {
    $message = $this->t('Expected count is correct');
}
elseif ($expected_count == 0) {
    $message = $this->t('Expected count is 0');
}
else {
    $message = $this->t('Expected count is unknown');
}

Writing code, both science and art, Zagreb, May 2017​

switch ($key) {
  case 'field_name':
    $checked_value = $field_storage->getName();
    break;

  case 'field_id':
  case 'field_storage_uuid':
    $checked_value = $field_storage->uuid();
    break;

  default:
    $checked_value = $field->get($key);
    break;
}

Writing code, both science and art, Zagreb, May 2017​

try {
  $this->migration->checkRequirements();
}
catch (RequirementsException $e) {
  $this->message->display(
    $this->t(
      'Migration @id did not meet the requirements. @message @requirements',
      array(
        '@id' => $this->migration->id(),
        '@message' => $e->getMessage(),
        '@requirements' => $e->getRequirementsString(),
      )
    ),
    'error'
  );

  return MigrationInterface::RESULT_FAILED;
}

Writing code, both science and art, Zagreb, May 2017​

{% if data.width %}
    {% trans %}
        width {{ data.width }}
    {% endtrans %}
{% elseif data.height %}
    {% trans %}
        height {{ data.height }}
    {% endtrans %}
{% endif %}
<?php if ($content): ?>
    <div class="content">
        <?php print $content; ?>
    </div>
<?php endif; ?>

Arrays

  • Short array syntax
  • Multiline or single line
  • Trailing comma

Writing code, both science and art, Zagreb, May 2017​

Writing code, both science and art, Zagreb, May 2017​

$test_samples = ['Save and continue', 'Anonymous', 'Language'];

$header['type'] = [
    'data' => $this->t('Field type'),
    'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
];

Naming conventions

  • Variables as snake_case or lowerCamelCase
  • Constants as UPPERCASE
  • Classes as UpperCamelCase
  • Methods and properties as lowerCamelCase

Writing code, both science and art, Zagreb, May 2017​

Comment your code

  • File doc block - @file, with exceptions
  • Functions and methods doc block
  • Hook implementations
  • {@inheritdoc}
  • Inline comments

Writing code, both science and art, Zagreb, May 2017​

Writing code, both science and art, Zagreb, May 2017​

<?php

/**
 * @file
 * Allows to ban individual IP addresses.
 */

use Drupal\Core\Routing\RouteMatchInterface;
/**
 * Get a translated version of the field item instance.
 *
 * To indicate that a field item applies to one translation of an entity and
 * not another, the property path must originate with a translation of the
 * entity. This is the reason for using target_instances, from which the
 * property path can be traversed up to the root.
 *
 * @param \Drupal\Core\Field\FieldItemInterface $field_item
 *   The untranslated field item instance.
 * @param $langcode
 *   The langcode.
 *
 * @return \Drupal\Core\Field\FieldItemInterface
 *   The translated field item instance.
 */
protected function createTranslatedInstance(FieldItemInterface $item, $langcode) {

Writing code, both science and art, Zagreb, May 2017​

/**
 * Implements hook_entity_bundle_info_alter().
 */
function forum_entity_bundle_info_alter(&$bundles) {

/**
 * Implements hook_ENTITY_TYPE_presave() for node entities.
 *
 * Assigns the forum taxonomy when adding a topic from within a forum.
 */
function forum_node_presave(EntityInterface $node) {

CSS

  • Very similar to the default formatting
  • Indentation, spaces, whitespace
  • New lines between rulesets, none between related rulesets
  • File and inline comments

Writing code, both science and art, Zagreb, May 2017​

JavaScript

  • File closure and strict mode
  • Naming conventions on variables, functions and constants
  • Do not use global variables
  • Function declaration and calls
  • Commenting your code

Writing code, both science and art, Zagreb, May 2017​

Writing code, both science and art, Zagreb, May 2017​

/**
 * @file
 * Attaches behavior for the Editor module.
 */

(function ($, Drupal, drupalSettings) {

  'use strict';

  ...

})(jQuery, Drupal, drupalSettings);

Writing code, both science and art, Zagreb, May 2017​

/**
  * Detaches editor behaviors from the field.
  *
  * @param {HTMLElement} field
  *   The textarea DOM element.
  * @param {object} format
  *   The text format that's being activated, from
  *   drupalSettings.editor.formats.
  * @param {string} trigger
  *   Trigger value from the detach behavior.
  */
Drupal.editorDetach = function (field, format, trigger) {
  if (format.editor) {
    Drupal.editors[format.editor].detach(field, format, trigger);

    // Restore the original value if the user didn't make any changes yet.
    if (field.getAttribute('data-editor-value-is-changed') === 'false') {
      field.value = field.getAttribute('data-editor-value-original');
    }
  }
};

Writing code, both science and art, Zagreb, May 2017​

/**
  * Enables editors on text_format elements.
  *
  * @type {Drupal~behavior}
  *
  * @prop {Drupal~behaviorAttach} attach
  *   Attaches an editor to an input element.
  * @prop {Drupal~behaviorDetach} detach
  *   Detaches an editor from an input element.
  */
Drupal.behaviors.editor = {
  attach: function (context, settings) {
    // If there are no editor settings, there are no editors to enable.
    if (!settings.editor) {
      return;
    }

    $(context).find('[data-editor-for]').once('editor').each(function () {
      var $this = $(this);
      
      ...
    });

  }
};

Twig

  • Looping over data
  • Attributes and filters
  • Comments - file doc block
  • Do not use any logic

Writing code, both science and art, Zagreb, May 2017​

Writing code, both science and art, Zagreb, May 2017​

{% for container in containers %}
  <div class="layout-column layout-column--half">
    {% for block in container.blocks %}
      {{ block }}
    {% endfor %}
  </div>
{% endfor %}

Writing code, both science and art, Zagreb, May 2017​

{#
/**
 * @file
 * Default theme implementation to display a block.
 *
 * Available variables:
 * - plugin_id: The ID of the block implementation.
 * - label: The configured label of the block if visible.
 * - configuration: A list of the block's configuration values.
 *   - label: The configured label for the block.
 *   - label_display: The display settings for the label.
 *   - provider: The module or other provider that provided this block plugin.
 *   - Block plugin specific settings will also be stored here.
 * - content: The content of this block.
 * - attributes: array of HTML attributes populated by modules, intended to
 *   be added to the main container tag of this template.
 *   - id: A valid HTML ID and guaranteed unique.
 * - title_attributes: Same as attributes, except applied to the main title
 *   tag that appears in the template.
 * - title_prefix: Additional output populated by modules, intended to be
 *   displayed in front of the main title tag that appears in the template.
 * - title_suffix: Additional output populated by modules, intended to be
 *   displayed after the main title tag that appears in the template.
 *
 * @see template_preprocess_block()
 *
 * @ingroup themeable
 */
#}

Object oriented code

  • Classes and interfaces
  • Methods and properties
  • Translate function, with placeholders
  • @variable, %variable, :variable
  • Dependency injection 

Writing code, both science and art, Zagreb, May 2017​

Writing code, both science and art, Zagreb, May 2017​

namespace Drupal\block\Entity;

use Drupal\Core\Cache\Cache;
...

/**
 * Defines a Block configuration entity class.
 *
 * @ConfigEntityType(
 * ...
 */
class Block extends ConfigEntityBase implements BlockInterface, EntityWithPluginCollectionInterface {

  /**
   * The ID of the block.
   *
   * @var string
   */
  protected $id;

  /**
   * The plugin collection that holds the block plugin for this entity.
   *
   * @var \Drupal\block\BlockPluginCollection
   */
  protected $pluginCollection;

  /**
   * {@inheritdoc}
   */
  public function getPlugin() {
    return $this->getPluginCollection()->get($this->plugin);
  }

  ...

}

Writing code, both science and art, Zagreb, May 2017​

/**
  * Constructs a ContentEntityForm object.
  *
  * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  *   The entity manager.
  * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
  *   The factory for the temp store object.
  */
public function __construct(EntityManagerInterface $entity_manager, PrivateTempStoreFactory $temp_store_factory) {
  parent::__construct($entity_manager);
  $this->tempStoreFactory = $temp_store_factory;
}

/**
  * {@inheritdoc}
  */
public static function create(ContainerInterface $container) {
  return new static(
    $container->get('entity.manager'),
    $container->get('user.private_tempstore')
  );
}

/**
  * {@inheritdoc}
  */
public function save(array $form, FormStateInterface $form_state) {
  $node = $this->entity;
  $insert = $node->isNew();
  $node->save();
  $node_link = $node->link($this->t('View'));

Writing code, both science and art, Zagreb, May 2017​

$this->t('Direction that text in this language is presented.');

$this->t('The %label search page has been updated.', [
  '%label' => $this->entity->label()
]);

What can (should) you do?

  • Set up your code editor
  • Use Coder and Codesniffer
  • Formatting comes a long way

Writing code, both science and art, Zagreb, May 2017​

Be consistent!

Writing code, both science and art, Zagreb, May 2017​

Simply making it work doesn't cut it.

Writing code, both science and art, Zagreb, May 2017​

@jnzavrl | jzavrl | janzavrl.me

@ndp_studio | ndp-studio.com

Writing code, both science and art, Zagreb, May 2017​

Questions, comments?

Feel free to give me a nudge outside.