Astuce Installer la dernière version de drush

Sur les dépôts des distributions linux, c'est souvent une vieille version de drush qui est disponible (ex: Debian 8.4 -> drush 5.x).
Voici comment installer la dernière.

Pré-requis

Composer et GIT doivent être installés.

Composer

sudo apt-get install curl
sudo curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
 
# Vérification
composer --version

GIT

sudo apt-get install git
 
# Vérification
git --version

Installation

  • Téléchargez drush :
    sudo git clone --depth 1 https://github.com/drush-ops/drush.git /usr/local/src/drush
  • Mettez-le à jour :

    cd /usr/local/src/drush
    sudo composer install
  • Créez les liens symboliques suivant :

    sudo ln -s /usr/local/src/drush/drush /usr/local/bin/drush
    sudo ln -s /usr/local/src/drush/drush.complete.sh /etc/bash_completion.d/drush
  • Vérifiez l'installation :

    drush --version

Astuce Surcharger l'affichage d'une page existante

Drupal 8 propose nativement des pages pour gérer l'inscription, la connexion, l'oubli de mot passe.

Malheureusement actuellement il n'y a pas de suggestion de template proposée. (Comme on peut le voir habituellement en commentaire dans le code source lorsque le mode debug est activé.)

Il faut donc procéder autrement et utiliser les hook_form_alter() et hook_theme() classiques.

Par exemple, pour surcharger le formulaire de la page oubli de mot de passe :

// mymodule.module
 
/**
 * Implements hook_form_alter()
 */
function mymodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
 
  // Si le formulaire est celui d'oubli de mot de passe
  if ($form_id == 'user_pass') {
    $form['#theme'] = ['my_register_form'];
  }
}
 
/**
 * Implements hook_themer()
 */
function mymodule_theme(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
 
  return [
    'my_password_form' => [
      'template' => 'user_forget_password_form',
      'render element' => 'form',
    ],
  ];
}

Explications :

  • Le hook_alter() permet de modifier le thème à utiliser pour le formulaire. Le thème choisi doit exister ou être déclaré dans votre module.
  • Le hook_theme() permet de déclarer le nouveau thème my_password_form et d'y affecter un template spécifique.

Remarque :

Par défaut, sans cette configuration, le template natif form.html.twig serait utilisé. Pour créer votre propre template il peut donc être pratique d'en faire une copie, la renommer (ici user_forget_password_form.html.twig) et de s'en servir comme base pour effectuer vos modifications.


Astuce Suivre les liens symboliques

Si vous utilisez Virtual Box sous Windows pour émuler des VM sous Linux, avec des répertoires partagés, vous aurez surement des problèmes de création de liens symboliques dans ces répertoires.

Il suffit de créer les liens symbolique côté Windows et non pas sous Linux.
Cependant, cela peut ne pas être suffisant, si vous utilisez une version VirtualBox 4.1.8 ou ultérieure.

Dans ce cas, il vous faut modifier un paramètre désactivé par défaut, via la commande suivante :

"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" setextradata "<nom_de_la_vm>" VBoxInternal2/SharedFoldersEnableSymlinksCreate/<nom_du_partage> 1

Astuce Bonnes pratiques jQuery

Isoler son code

Pour éviter que ses variables et fonctions ne soient disponibles partout et ne rentrent en conflit entre-elles il est important d'isoler son code.

Pour cela, plusieurs solution sont possibles, en fonction des cas.

Code à usage unique

(function($) {
 
// Code à exécuter
// [...]
})(jQuery);

Code à usage régulier

$.myNamespace = {
    multiply: function(x,y) {
        return (x * y);
    }
};
 
// Appel
var calc = $.myNamespace.multiply(1, 1);

Traitement "oneshot" appliqué sur des éléments du DOM

$.fn.myFunction = function(params) {
 
    this.css('color', 'red');
    return this;
};
 
// Appel
$('.element').myFonction();

Explication :

On crée un plugin jQuery, que l'on appelle sur un ensemble d'éléments du DOM (ex: $('.element')). Tous ces éléments sont impactés une fois, sans mémorisation de leur état (= "stateless").

Traitements appliqués sur des éléments du DOM

$.widget('ui.trsb.truc.myWidget', [$.ui.widgetParent,]{
    "options": {},
    myWidgetFunction(){...},
    _create: function(){...},
    _destroy: function(){...},
    _setOptions: function(key, value){...},
});
 
// Appel
$('.element').myWidget();
$('.element').myWidgetFunction();

Explication :

On crée un widget jQuery (nécessite jQueryUI), que l'on appelle sur un ensemble d'éléments du DOM (ex: $('.element')). Tous ces éléments sont impactés avec mémorisation de leur état (= "statefull"). Les widgets sont utiles lorsque l'on veut proposer plusieurs méthodes d'action (ex: un accordéon avec une pour dérouler, et une autre pour replier).

Norme de codage et optimisation

Sélecteurs

// Pour des raisons de compatibilité, préférez
$('#container').find('.children').hide();
 
// à
 
$('#container .children').hide();
 
// ou à
 
$('.children', '#container').hide();

Éviter le tout jQuery

$('#link').on('click', function() {
 
    // Préférez
    this.id
 
    // à
    $this.attr(id');
});

Chargement du DOM

// Préférez
 
$(function() {
 
});
 
// à
 
$(document).ready(function() {
 
});

Gestion des évènements

// Préférez
 
$('#element').on('click', function() {
 
});
 
// à
 
$('#element').click(function() {
 
});

No conflict

// Préférer
 
(function($) {
 // [...] Code sans conflit
})(jQuery);
 
// à
 
jQuery.noConflict();
// [...] Code sans conflit

Appels AJAX

// Préférez
 
$.ajax()
 
// à
 
$.getJSON();

Astuce Ajouter des pages de configuration

Pour rendre votre site plus facilement paramétrable, il est utile de fournir une interface d'administration pour modifier telle ou telle option.

Ces options seront ensuite accessibles partout dans votre code :

$config = \Drupal::config('mon_module.settings');
$my_option_value = $config->get('my_option');

Comme son prédécesseur, Drupal 8 permet de générer rapidement ces interfaces, ainsi que les éléments du menu d'administration correspondant :

admin_bar

Pour générer deux pages de formulaires avec des onglets pour passer de l'un à l'autre, vous aurez besoin des fichiers suivants :

settings_arbo

Remarque :

L'exemple qui suit requiert d'activer ces deux modules : admin_toolbar et admin_toolbar_tools.

Création d'un formulaire d'administration

Voici un exemple de formulaire d'administration affichant trois champs : numérique, texte riche et email.

Structure générale

<?php
 
namespace Drupal\my_module\Form;
 
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
 
/**
 * Gère le formulaire de configuration générale pour le module.
 */
class GlobalSettingsForm extends ConfigFormBase {
 
  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'my_module_settings_form';
  }
 
  /**
  * {@inheritdoc}
  */
  protected function getEditableConfigNames() {
    return [
      'my_module.settings',
    ];
  }
 
  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
 
  }
 
  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
 
  }
 
}

Explications :

  • Le formulaire hérite de la classe abstraite ConfigFormBase, fournie par Drupal.
  • En plus des méthodes getFormId(), buildForm() et submitForm() habituelles, elle impose que la méthode getEditableConfigNames() soit implémentée.
  • Cette méthode permet de définir un "namespace" pour votre configuration. Chaque propriété devra avoir un id unique au sein du même "namespace".

Méthodes buildForm() et submitForm()

/**
  * {@inheritdoc}
  */
public function buildForm(array $form, FormStateInterface $form_state) {
 
  // Récupération de la configuration avec le "namespace" my_module.settings
  $config = $this->config('my_module.settings');
 
  $form['nb_news_homepage'] = array(
    '#type' => 'number',
    '#title' => $this->t('Nombre d\'actualités afichées en page d\'accueil'),
    '#default_value' => $config->get('nb_news_homepage'),
  );
 
  $form['welcome_text'] = array(
    '#type' => 'text_format',
    '#title' => $this->t('Texte de bienvenue'),
    '#description' => $this->t('Texte affiché en page d\'accueil.'),
    '#default_value' => $config->get('welcome_text'),
  );
 
  $form['contact_receiver_email'] = array(
    '#type' => 'email',
    '#title' => $this->t('Adresse email du destinataire pour le formulaire de contact'),
    '#default_value' => $config->get('contact_receiver_email'),
  );
 
  return parent::buildForm($form, $form_state);
}
 
/**
 * {@inheritdoc}
 */
public function submitForm(array &$form, FormStateInterface $form_state) {
  $this->config('my_module.settings')
    ->set('nb_news_homepage', $form_state->getValue('nb_news_homepage', 5))
    ->set('welcome_text', $form_state->getValue('welcome_text', '<p>Texte de bienvenue à changer.</p>')['value'])
    ->set('contact_receiver_email', $form_state->getValue('contact_receiver_email', 'admin@monsite.com'))
    ->save();
 
  parent::submitForm($form, $form_state);
}

Explications :

La méthode buildForm() est semblable à celle d'un formulaire classique. A noter cependant :

  • Pour récupérer les valeurs présentes en base et préremplir les champs, on utilise le code la méthode config(), avec le "namespace" définit précédemment
  • Chaque valeur est accessible individuellement via un simple getter
  • La méthode parente est appelée

La méthode submitForm() va enregistrer les données soumises en base :

  • Les configurations actuelles sont récupérées
  • Les nouvelles valeurs sont mises à jour, puis sauvegardées
  • La méthode parente est appelée

Remarque :

Pour un champ texte riche, la méhode getValue() proposée par FormStateInterface retourne un tableau et non pas une valeur enregistrable en base.

Il faut penser à ajouter ['value'] derrière, pour avoir une chaîne de caractère utilisable.

Configuration du menu

my_module.routing.yml

C'est le fichier classique de Drupal, permettant de lier des URL à vos contrôleurs et formulaires.

# Page générale listant les pages de configuration du module
my_module.overview:
  path: '/admin/config/my_module'
  defaults:
    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
    _title: 'Mon module - Configuration'
    link_id: 'my_module.overview'
  requirements:
    _permission: 'administer site configuration'
 
# Page de configuration générale du module
my_module.settings:
  path: '/admin/config/my_module/general'
  defaults:
    _form: '\Drupal\my_module\Form\GlobalSettingsForm'
    _title: 'Mon module - Configuration générale'
  requirements:
    _permission: 'administer site configuration'
 
# Page de configuration des webservices du module
my_module.webservices.settings:
  path: '/admin/config/my_module/webservices'
  defaults:
    _form: '\Drupal\my_module\Form\WebservicesSettingsForm'
    _title: 'Mon module - Configuration des webservices'
  requirements:
    _permission: 'administer site configuration'

Explications :

  • Les deux dernières routes sont classiques. Elles pointes vers vos nouvelles pages de formulaire de configuration.
  • La première pointe vers un contrôleur fourni par Drupal, qui permet de lister des sous-pages (ex: http://www.monsite.com/admin/config/people)

my_module.links.menu.yml

Ce fichier définit de nouveaux éléments dans le menu d’administration.

# Page générale listant les pages de configuration du module
my_module.overview:
  title: 'Mon module'
  parent: system.admin_config
  description: 'Voir les pages de configuration du module "Mon module".'
  route_name: my_module.overview
  weight: -100
 
# Page de configuration générale du module
my_module.settings:
  title: 'Général'
  parent: my_module.overview
  description: 'Gérer la configuration générale du module.'
  route_name: my_module.settings
  weight: -10
 
# Page de configuration des webservices du module
my_module.webservice.settings:
  title: 'Webservices'
  parent: my_module.overview
  description: 'Gérer la configuration des webservices.'
  route_name: my_module.webservices.settings
  weight: -5

Explication :

Pour chaque lien souhaité, on définit :

  • le libellé (title)
  • la description au survol (attribut title pour le lien généré)
  • l'élément de menu parent
  • la route vers laquelle pointer
  • le poids de l'élément (le plus petit apparaîtra en premier)

Dans cet exemple, on à la page "overview", parente des deux autres.

my_module.links.task.yml

Ce fichier définit des onglets accessibles dans les pages d'administration.

my_module.settings:
  route_name: my_module.settings
  title: 'Général'
  base_route: my_module.settings

my_module.webservices.settings:
  route_name: my_module.webservices.settings
  title: 'Webservices'
  base_route: my_module.settings

Explication :

Pour chaque onglet souhaité, on définit :

  • le libellé (title)
  • la route vers laquelle pointer
  • l'onglet principal

Astuce Commandes drush utiles

Les commandes drush pour Drupal 8 sont en partie identiques à celles pour Drupal 7.
En fait il s'agit surtout de la version Drush et non pas de celle de Drupal. Pour Drupal 8, il est conseillé d'utiliser la version 8.x de drush.

Voici une liste de commandes drush bien pratiques :

Features

Fonction

Commande

Exporter les configurations du site

drush cex

Importer les configurations du site

drush cim

Modules

Fonction

Commande

Information sur un module

drush pmi nom_module

Télécharger un module

drush dl nom_module

Activer un module

drush en nom_module

Désinstaller un module

drush pmu nom_module

Mettre à jour les tables en base d'un module

drush updb nom_module

Liste des modules communautaires activés

drush pm-list --pipe --type=module --status=enabled --no-core

Liste des modules du cœur activés

drush pm-list --pipe --type=module --status=enabled --core

Base de données

Fonction

Commande

Exécuter une commande sql

drush sqlc "SELECT * FROM node;"

Mettre à jour les tables en base pour tous les modules

drush updb (utile après une mise à jour de sécurité)

Autres

Fonction

Commande

Vider tous les caches

drush cr

Modifier le mot de passe d'un utilisateur

drush upwd --password="nouveau_mot_de_passe" login_utilisateur

Exécuter une tâche planifiée

drush php-eval 'monmodule_cron();'

Exécuter du code PHP

drush php-eval 'echo "je suis du code php exécuté";'


Astuce Ajouter un mode d'affichage à un nœud

Dans Drupal 7, pour ajouter un nouveau mode d'affichage il fallait utiliser un hook (cf: cet autre article).

Dans Drupal 8.x, tout est faisable en back-office, via Structure > Modes d'affichage. Ensuite, comme avant, il faut activer le nouveau mode pour le type de nœud correspondant :

modes_affichage

Remarque :

Dans Drupal 8, les modes d'affichage sont cloisonnés par entité. Si vous voulez un mode d'affichage "Liste" pour les utilisateurs et pour les nœuds, il faudra en créer deux.


Astuce Créer un contenu programmatiquement

Pour créer un nouveau contenu (ou n'importe quelle entité), quelques lignes suffisent :

<?php
 
use Drupal\node\Entity\Node;
use Drupal\Core\Entity\EntityStorageException;
$node = Node::create(array(
  'type' => 'article',
  'langcode' => 'fr',
  'uid' => '1',
  'status' => 1,
));
$node->setTitle('Mon premier article');
$node->set('field_my_text', 'du texte');
$node->set('field_my_float', 150.42);
$node->set('field_my_date', date('Y-m-d'));
 
try {
  $node->save();
}
catch (EntityStorageException $e) {
  \Drupal::logger('fastt_prestation')->error('La création de l\'article a échouée : ' . $e->getMessage(), array(
    'exception' => $e
  ));
  $node = null;
}

Explications :

  • Les arguments de la méthode create() permettent de définir le type de nœud, son créateur, son statut, sa langue, ...
  • Tous les champs sont ensuite valués via la méthode set().

Astuce Theming : définir une nouvelle apparence pour un élément

La plupart du temps dans Drupal, on définit la manière d'afficher des éléments via un tableau de theming côté PHP.

Par exemple, pour un bloc, on peut avoir un tableau du genre :

$build = array(
  '#cache' => array(
    'contexts' => array('user'),
    'max-age' => Cache::PERMANENT,
  ),
  '#markup' => '<p>Hello ' . $who . '</p>',
);

Dans cet exemple, on ne précise pas l'habillage à utiliser. Drupal sélectionnera donc un template par défaut en fonction du type de l'élément (block.html.twig dans le cas d'un bloc).

Habillage à utiliser

Pour utiliser votre propre template, il faut modifier le tableau et remplacer #markup par #theme :

$build = array(
  '#cache' => array(
    'contexts' => array('user'),
    'max-age' => Cache::PERMANENT,
  ),
  '#theme' => 'my_hello',
  '#who' => $who,
);

Déclaration de l'habillage

Pour que Drupal trouve votre habillage, vous devez implémenter le hook_theme() dans votre module.

my_module.module :

/**
 * Implements hook_theme().
 */
function my_module_theme() {
  return [
    'my_hello' => [
      'template' => 'my_hello_box',
      'variables' => [
        'who' => 'World',
      ],
    ]
  ];
}

Explications :

  • La fonction définit un nouvel habillage my_hello.
  • Le template à utiliser est my_hello_box.html.twig.
  • La variable who sera transmise au template, avec la valeur 'World' par défaut.

Template

Le template my_hello_box.html.twig placé dans le répertoire templates/ de votre module peut ressembler à ça :

{# Affichage d'un message Hello sous forme de boîte #}
<div class="box">
    <p>{{ 'Hello %who !'|t({ 'who': who }) }}</p>
</div>

Remarque :

Vous pouvez placer votre template dans n'importe quel sous répertoire de templates/. Drupal saura le trouver.