00:00:00

Formation Développement Drupal

Notes

Qui suis-je ?

Simon Georges


@simongeorges sur le web
Expert Drupal / SEO

Notes

Objectifs de la formation

  • Appréhender l'environnement de développement Drupal
  • Comprendre les concepts et l'API Drupal 
  • Modifier le comportement d'un module existant 
  • Créer un module
  • Créer un profil d'installation

Prérequis

  • Connaitre PHP et avoir développé quelques scripts
  • Avoir construit un site avec Drupal
  • Notions de HTML, CSS, JavaScript et requêtes SQL

Notes

Au sommaire

  • Rappels PHP
  • Environnement de développement
  • Quelques outils utiles
  • Bonnes pratiques, standards de code, documentation
  • Architecture de Drupal & concepts de base
  • Création du squelette d'un module
  • Les premiers hooks
  • Les différentes API
  • Les nœuds, le contenu et les droits d'accès
  • L'alteration des modules
  • La form API
  • Les mails
  • Créer un thème basique
  • Les profils d'installation

Notes

Quelques rappels

Notes

Rapidement, ce que vous devez savoir

  • POO
  • Composer
  • Symfony
  • Routage et controlleurs
  • Plugins
  • Injection de dépendences et le conteneur de services

Notes

/!\ Avertissement /!\

"Going off the island"

  • Actuellement en version 8.8.x
  • Beaucoup de choses ont changé depuis Drupal 7

Notes

Notes

Les normes PHP

  • Viennent du PHP Framework Interoperability Group (FIG)
  • Différentes normes
    • PSR-0 : autoloader standard
    • PSR-1 : normes de codage de base (Drupal les suit presque)
    • PSR-2 : normes de codage plus poussées (Drupal les suit presque)
    • PSR-3 : interface du logger (pas implémentée dans Drupal)
    • PSR-4 : autoloader amélioré (choisi par Drupal) : https://www.drupal.org/node/2156625
  • À suivre : Symfony a quitté le FIG (après Laravel, Propel, Guzzle et Doctrine)

Notes

Symfony

Notes

YAML

key: 'value'
tableau:
  - valeur 1
  - valeur 2

Notes

Composer

Notes

Environnement de développement

Notes

Le serveur Web

  • xAMP (Apache, MySQL, PHP) conseillé
  • D'autres possibilités : Nginx / IIS, PostgreSQL
  • Liste des languages utilisés :
    • SQL
    • PHP
    • Javascript
    • HTML
    • CSS

Notes

TP: Installer Drupal 8

  • Télécharger drupal : https://www.drupal.org/project/drupal
  • Installer le profil d'installation Standard
  • Paramètres
    • Nom du site : Formation
    • Adresse de courriel : formation@example.org
    • Utilisateur : admin
    • Mot de passe : admin

Notes

L'éditeur de code

  • Le meilleur est celui que vous maitrisez
  • Différence IDE/Editeur simple :
    • Autocomplétion
    • XDebug
    • Refactoring
    • Erreurs de syntaxe
  • Possibilité de télécharger des configurations
  • Exemples : PHPStorm / Eclipse / Netbeans / Atom / Vim

Notes

Les standards de codage*

  • Indentation, espaces : lisibilité du code
  • Nommage, toujours commencer par le nom système : éviter les conflits
    • fonctions
    • constantes
    • variables persistantes
    • classes
    • fichiers
  • Tags <?php non fermés : éviter l'envoi du buffer

À connaître pour comprendre et être compris

* basés sur le standard PEAR

Notes

TP: Configuration de PHPStorm

  • Ouvrir le dossier Drupal dans PHPStorm
  • Afficher les messages de journal de PHPStorm
  • Cliquer sur "Enable Drupal support" et configurer le chemin
  • Cliquer sur "Fix extensions"

Avantages :

  • code style implémenté
  • navigation dans les hooks (appels et déclarations)
  • installation d'XDebug facile pour PHPStorm

Notes

Les modules Drupal utiles au développement

Notes

TP: Drush

  • Installer et lancer Drush (vendor/bin/drush)
  • Regarder la liste des commandes 
  • Sauvegarder la base de données 
  • Installer les modules utiles au développement : devel, masquerade, examples
  • Désactiver le module history

Notes

Exemples de commandes souvent utilisées

  • drush cr (cache-rebuild)
  • drush uli (user-login)
  • drush sql-dump
  • drush site-install
  • drush archive-dump / drush archive-restore
  • drush cim / drush cex

Notes

TP : Console

  • Intaller la console Drupal
  • Vérifier que c'est correctement installé
  • Regarder la liste des commandes (vendor/bin/drupal list)

Notes

Exemples de commandes souvent utilisées

  • drupal site:mode dev (désactivée depuis la rc12)
  • drupal generate:module
  • drupal generate:plugin:block
  • drupal generate:routesubscriber
  • drupal generate:form:config
  • drupal generate:entity

Notes

Le meilleur moyen d'installer Drupal 8

  • composer create-project drupal-composer/drupal-project:8.x-dev some-dir --stability dev --no-interaction

  • composer create-project drupal/drupal my_site_name installe un nouveau site

  • composer require drupal/core:8.8.1 --update-with-dependencies met à jour le cœur

  • Attention, encore quelques problèmes avec Composer

Notes

Git et la gestion de versions

  • Utilisé par beaucoup de développeurs dans le milieu du web

  • Très utile pour patcher des modules car utilisé par la communauté Drupal et sur drupal.org

  • Une connaissance basique de quelques commandes suffit

    • git clone
    • git add
    • git commit
    • git diff
  • Possibilité d'avoir une interface graphique : SourceTree, Gitg, GitLab, GitHub

Notes

L'architecture de Drupal

Notes

Présentation de l'arborescence

Cœur de Drupal
Modules de tous les sites
Profils d'installations
Répertoire spécifique au site
Répertoire d'upload par défaut
Fichiers de configuration
-
-
-
-
-
Thèmes de tous les sites


Où travailler ? Dans un profil d'installation custom ou dans un sous-repertoire custom

/!\ Le hack du core et l'avenir des chatons

Notes

Notes

Notes

TP: Jetons un œil à la base de données

  • se familiariser avec PhpMyAdmin
  • identifier les tables des modules actuellement activés
  • identifier les autres : cache, variable, registry
  • identifier la table contenant la configuration
  • https://www.drupal.org/node/1785994

Notes

Qu'est ce qu'un module ?

  • .info.yml (https://www.drupal.org/node/2000204)
    • name
    • type: module /!\
  • Avant 8.7.7
    • core: 8.x
  • Après 8.7.7
    • core_version_requirement: ^8.8 || ^9
  • .module (souvent vide en D8)
  • .install facultatif (configuration désormais indépendante)
  • répertoire "config/install" pour la configuration
  • répertoire "src" pour les Plugins, Controller, Form, ...

Notes

Structure d'un module

https://www.drupal.org/node/2560405

Notes

Fil rouge : le module Premium

  • Créer deux permissions pour les rôles, une pouvant affecter le status premium aux contenus et l'autre le voir
  • Créer un bloc affichant si l'utilisateur a la permission de voir les contenus premium
  • Administrer les types de contenus concernés par le statut premium
  • Altérer le formulaire d'édition de nœud pour ajouter le statut premium
  • Envoyer un mail aux utilisateurs ayant la permission de voir les contenus premium lorsque qu'un nouveau contenu premium apparait sur le site
  • Lister les contenus avec le statut premium
  • Créer un style d'image pour illustrer les contenus premium
  • Créer une fonction de theme pour afficher le statut premium d'un nœud
  • Créer des tests pour vérifier le bon fonctionnement du module

Créer ce module : il doit simplement apparaître dans la liste des modules.

Notes

La création d'un module

Notes

Avant de commencer

Votre bible : api.drupal.org

Recense toutes les fonctions de Drupal, et leur documentation. Un IDE aura cette même documentation dans le code.

Aborde certains topics en profondeur : form API, schema API, hooks, etc

https://api.drupal.org/api/drupal/core%21core.api.php/group/extending/8.6.x


Votre guide : modules examples

Pour chaque concept de Drupal, des exemples concrets d'utilisation (ajax, form, blocks, cache, render, cron, dbtng, email, menu, node_access, theming, ...).

Ils sont fonctionnels : on peut les activer et voir leur impact en terme d'interface.

Très bien documentés, ne pas hésiter à lire, comprendre et reprendre le code de ces modules.

Notes

Les permissions

  • Créer un fichier .permissions.yml
  • Créer les deux permissions
  • Créer un role contributeur pouvant affecter le statut premium
  • Créer un role premium pouvant voir le statut premium
  • Créer un utilisateur pour chaque rôle

Aide (création de permissions)

vendor/bin/drupal generate:permissions

Notes

Et pour des permissions dynamiques ?

permission_callbacks:
  - \Drupal\my_module\MyClass::myFunction

Notes

Les plugins & les annotations

Notes

Les blocs

fichier src/Plugin/Block/TestBlock.php

namespace Drupal\mon_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
 * Provides a 'Test' block.
 * 
 * @Block(
 *   id = "test_block",
 *   admin_label = @Translation("Test block"),
 * )
 */
class TestBlock extends BlockBase {
  public function build() {
    return array('#markup' => '',);
  }

  public function blockAccess(AccountInterface $account) { }

  public function blockForm($form, FormStateInterface $form_state) { }

  public function blockSubmit($form, FormStateInterface $form_state) { }
}

Notes

TP: Notre premier bloc

Créer un bloc :

  • dont le titre côté administration est "Statut premium de l'utilisateur"
  • dont le delta (nom machine) est premium_status
  • qui affiche "Vous pouvez voir les contenus premium" ou "Vous ne pouvez pas voir les contenus premium"

Aide (création de bloc)

vendor/bin/drupal generate:plugin:block

Aide (vérification des permissions)

\Drupal::currentUser()->hasPermission('view premium');

Attention au cache d'implementations

Notes

Render Arrays

Les render arrays sont les blocs constituant une page Drupal. Ce sont des arrays PHP qui définissent des données (c-a-d la structure) ; On est obligés de produire des render arrays. Ceci afin qu'ils puissent être modifiés via les hooks d'altérations ou par la couche de theming.

Les propriétés sont toujours préfixées par un # et la propriété par défaut est #markup, elle permet d'indiquer du balisage simple. Un render array est converti en HTML avec la fonction render();

// Un render array simple
'ma_cle1' => array(
  '#markup' => "<h2>Du texte basique</h2>",
),

// Des propriétés utiles
'ma_cle2' => array(
  '#markup' => "Du texte basique",
  '#prefix' => '<h2>',
  '#suffix' => '</h2>',
),

Notes

Paramètres du render array et propriétés

Une fonction de #theme peut être renseignée ainsi que ses paramètres (https://www.drupal.org/developing/api/8/render/arrays)

// Un render array qui produit un tableau HTML
'ma_cle1' => array(
  '#theme' => 'table',
  '#header' => $header,
  '#rows' => $rows,
  '#empty' => "Aucune donnée pour ce tableau",
),

Des propriétés utiles :

  • #type: Le type d'élement
  • #cache: contexts, tags, ... /!\
  • #markup: Pour fournir directement de l'HTML
  • #pre_render / #post_render: agit sur le tableau
  • #prefix / #suffix, #weight, #attached, #access, ...

Notes

Render Arrays

// Un render array en html
render($array);
// (quasiment jamais invoqué directement)

// Un objet en render array
$object->toRenderable();

Notes

TP

Ajouter un '<h3>' autour du bloc précédent

Notes

TP: Manipuler les render arrays

Altérer le bloc dans un hook_block_view_BASE_ID_alter() afin de changer le <h3> en <h4>

-> Impossible à faire directement... Il faut ajouter un #pre_render dans le hook_block_view_alter(), et la fonction appelée dans le pre_render aura accès au $build du contenu du bloc.

Notes

Système de routage / menus

Quelques définitions :

  • routage : faire pointer une route (node/{node}) à une action (afficher un noeud)
  • chemin (ou path) : route dont les arguments sont définis (ex: node/123 est un chemin, pointant vers la route node/{node} où l'argument est 123)
  • lien de menu : Texte (ou titre) pointant vers un chemin
  • alias : associe un chemin système (node/123) vers un chemin arbitraire renseigné par le contributeur (mon-noeud)

Les propriétés d'une route

  • _Permissions
  • Les arguments sont nommés ({node}) et peuvent être chargés dans le Controller (en les typant avec une classe)
  • Vous pouvez passer des paramètres fixes au controller en les indiquant dans la route

Notes

Les controllers

fichier src/Controller/ModuleController.php :

<?php
namespace Drupal\mon_module\Controller;

use Drupal\Core\Controller\ControllerBase;

/**
 * Provides a basic controller.
 */
class ModuleController extends ControllerBase {
  public function abc_view() {
    return array('#markup' => '',);
  }
}

Notes

  • Type d'élément de menu

    • *.routing.yml -> définit une URL
    • *.links.menu.yml -> lien de menu dans l'arborescence
    • *.links.task.yml -> onglet
    • *.links.action.yml -> "action" (back-office)
  • Paramètres de routage : https://www.drupal.org/node/2092643

Notes

Exemples de routage

#.routing.yml
mymodule.abc_view:
  path: '/abc/def'
  defaults:
    _title: 'My ABC page'
    _controller: '\Drupal\module\Controller\ModuleController::abc_view'
  requirements:
    _permission: 'access my module'

# , = ET
  requirements:
    _permission: 'access my module,access content'
# + = OU
  requirements:
    _permission: 'access my module+access content'
# Personnalisé
  requirements:
    _custom_access: '\Drupal\my_module\MyClass::my_function'

La function renvoit alors AccessResult::allowed() ou AccessResult::forbidden()

Notes

Ajout de liens / onglets

#.links.menu.yml
mymodule.abc_view_tab:
  title: 'My ABC page'
  route_name: mymodule.abc_view
  description: 'Displays my ABC page'
  parent: mymodule.abc_view

#.links.task.yml
mymodule.abc_view_edit:
  title: 'Edit'
  route_name: mymodule.abc_view_edit
  base_route: mymodule.abc_view

#.links.action.yml pour les actions

Notes

TP: Création de page

Créer une page Suis-je Premium ? reproduisant le comportement du bloc

  • url : suis-je-premium

Créer une page Est-il Premium ? affichant la même chose, mais avec en argument l'uid de l'utilisateur

  • url d'exemple : est-il-premium/2

Créer une page Page Premium avec du contenu "Lorem Ipsum" et ne s'affichant que si l'utilisateur courant a la permission de voir le contenu premium

  • url : page-premium

Aide (création d'une route et d'un controller)

vendor/bin/drupal generate:controller

Notes

Quelques routes spéciales

  • <front>
  • <nolink>

Pour voir les routes, utilisez la console : drupal debug:router

Notes

Comment convertir les paramètres dans les routes

Utiliser des ParamConverter

Notes

Gestion des nodes et des users

Quelques fonctions de l'API à connaitre :

  • \Drupal::currentUser() : utilisateur actuellement connecté
  • Node::load() et Node::loadMultiple() pour charger des nœuds
  • User::load() et User::loadMultiple() pour charger des utilisateurs
  • \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($nids);
  • $entity->save() pour enregistrer un nœud, un utilisateur, ...
  • $user->getDisplayName() pour afficher un nom d'utilisateur
  • Node::create(['type' => article])->save();
  • $node->set('body' => ['value' => 'My body']); $node->save();

Notes

Gestion de la base de données

Requête en base de données

$db = \Drupal::database();
$result = $db->select();
$result = $db->insert();
$result = $db->delete();
$result = $db->udpate();
$result = $db->merge();

Requête sur le modèle objet

$ids = \Drupal::entityQuery('user')->condition('name', 'test')->execute();
$users = User::loadMultiple($ids);

Documentation complète

Notes

DBTNG : DataBase The Next Generation (issu de Drupal 7)

$results = $db->select('contact', 'c')
              ->fields('c')
              ->condition('created', REQUEST_TIME)
              ->execute() // ->fetch*()
              ;
foreach ($results as $result) {
  // faire qqch
}

Récupération de résultats :

  • fetchField() : la première colonne du premier résultat
  • fetchCol() : la première colonne sous forme d'array
  • fetchAssoc() : le premier résultat sous forme d'objet
  • fetchAllAssoc() : tous les résultats sous forme d'objet
  • fetchAllKeyed() : tous les résultats sous forme de tableau indexé par la 1ere colonne avec pour valeur la 2e

Notes

Gestion des URLs et des paths

Quelques fonctions

  • $this->redirect('contact.site_page'); -> redirection
  • global $base_url -> http://monsite.com
  • base_path() -> / ou /mon-dossier-drupal
  • drupal_get_path() (module, theme) -> chemin vers un module ou un thème (drupal_get_path('module', 'devel') donne modules/devel)

L'objet URL

use Drupal\Core\Url;
// Récupérer une Url.
$url = Url::fromRoute('contact.site_page', array())->toString();
$url = Url::fromUserInput('/contact')->toString();
// Ou générer un lien.
$link = Link::createFromRoute('text', 'route');
$link = Link::fromTextAndUrl('text', $url);

Notes

TP: Création de page

Créer une page Utilisateurs premium listant les utilisateurs du site ayant un accès premium.

  • Créer une page
  • Récupérer les utilisateurs ayant un rôle permettant de voir le contenu premium (->condition('roles', '...'))
  • Les afficher d'abord dans une liste ('#theme' => 'item_list')
  • Modifier pour les afficher dans un tableau ('#theme' => 'table') avec Identifiant, Nom, Lien vers son profil

Rappels (création de page)

vendor/bin/drupal generate:controller

Rappels (requêtes)

$ids = \Drupal::entityQuery('user')
  ->condition('roles', 'premium')
  ->execute();
$users = User::loadMultiple($ids);

Documentation de #theme => item_list

Notes

Les hooks

  • Concept historique Drupal
  • Implémenter hook_form_alter() donnera mon_module_form_alter()
  • Poids des modules et altération
  • Répondent à des déclencheurs
  • Des hooks peuvent être déclarés par des modules contrib
  • Rappel: on ne « hack » JAMAIS le core (sauf en cas de module bogué)
  • Tend à disparaître avec Drupal 8 (Plugins, yml, events), mais existe encore...
  • Les implémentations sont mise en cache
  • Liste des hooks https://api.drupal.org/api/drupal/core!core.api.php/group/hooks/8

Notes

Altérer le comportement des modules existants

Solution "historique" : les hooks

  • hook_XXXXXXX_alter() : permettent de modifier des données créés par d'autres modules
  • Liste des hooks

"Nouveauté" Drupal 8 : les Events symfony

  • On "s'inscrit" à un événement (via un service) pour que le système nous appelle automatiquement et qu'on puisse réagir
  • Liste des Events

Notes

Concrètement

Une classe pour la réponse à l'évènement

namespace Drupal\my_module\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
class MyModuleSubscriber implements EventSubscriberInterface {
  static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = array('my_function');
    return $events;
  }
  function my_function(GetResponseEvent $event) {
    $event->setResponse(new RedirectResponse('http://example.com/'));
  }
}

Un service (fichier my_module.services.yml)

services:
  my_module.redirect_all:
    class: Drupal\my_module\EventSubscriber\MyModuleSubscriber
    tags:
      - {name: event_subscriber}

Notes

Exemple : modification du routage des autres modules

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

class RouteSubscriber extends RouteSubscriberBase {
  protected function alterRoutes(RouteCollection $collection) {
    if ($route = $collection->get('user.login')) {
      $route->setPath('/login');
    }
    if ($route = $collection->get('user.logout')) {
      $route->setRequirement('_access', 'FALSE');
    }
  }
}

C'est un event...

services:
  example.route_subscriber:
    class: Drupal\example\Routing\RouteSubscriber
    tags:
      - { name: event_subscriber }

https://www.drupal.org/node/2187643

Notes

TP : Events & Hooks

Events

Empêcher d'accéder à l'édition du profil d'un utilisateur (/user/{user}/edit) en interdisant l'accès à la route.

vendor/bin/drupal generate:routesubscriber

Hooks

Faites la même chose en supprimant juste le lien de menu (hook_menu_local_tasks_alter)

Notes

Bonus : déclencher des évènements

$dispatcher = \Drupal::service('event_dispatcher');
$dispatcher->dispatch('my_object.my_event', $params);

Notes

Les évènements déclenchés par le cœur

  • kernel.request : Au début de la gestion de la requête
  • kernel.response : Une fois que la réponse est créée
  • routing.route_dynamic : Permet aux modules d'enregistrer d'autres routes
  • routing.route_alter : Permet de modifier les routes existantes

Notes

Que se passe-t-il sur Kernel.request ?

  • AuthenticationSubscriber : Charge la session et initialize currentUser().
  • LanguageRequestSubscriber : Détecte la langue courante
  • PathSubscriber : Convertit l'URL en chemin système
  • LegacyRequestSubscriber : Permet de définir un thème par défaut
  • MaintenanceModeSubscriber : Affiche la page de maintenance si besoin
  • RouteListener : Récupère le router chargé entièrement
  • AccessSubscriber : Vérifie que le visiteur a accès à la route

Notes

Notes

Un formulaire est une structure déclarative composée d'éléments de la form API. La majeure partie des traitements est effectuée par celle-ci, rendant la création ou la modification de formulaire rapide et sécurisée.

Référentiel des composants disponibles sur api.drupal.org

\Drupal::formBuilder()->getForm('Drupal\mymodule\MyModuleForm');

// Déclaration
public function getFormId() {
  return 'my_module_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}
public function validateForm(array &$form, FormStateInterface $form_state) {
  // Logique de validation.
  $form_state->setErrorByName('form_field', 'message');
}
public function submitForm(array &$form, FormStateInterface $form_state) {
  // Traitement des données soumises.
}

Notes

Traitement des données

Les données soumises et validées sont contenues dans $form_state->getValue('key').

Après exécution du _submit(), l'utilisateur est redirigé vers le formulaire vidé de ses valeurs, ou bien vers une route définie par $form_state->setRedirectUrl($url)

Chaque formulaire a un identifiant unique qui permet de l'altérer facilement par les autres modules.

Notes

Appeler un formulaire directement depuis le routage

example.form:
  path: '/example-form'
  defaults:
    _title: 'Example form'
    _form: '\Drupal\mymodule\Form\ExampleForm'

Notes

TP : Form API

But: définir pour quels types de contenu la fonctionnalité premium est activée. C'est-à-dire, sur quels types on affichera l'option "Contenu premium" dans les formulaires de création ou de modification de nœud.

  • Créer un formulaire listant les types de contenu avec pour chaque une checkbox


Article
Page de base

Rappels (création de formulaire)

vendor/bin/drupal generate:form

Notes

TP : Form API

  • Valider le fait qu'on ne peut pas choisir le type de contenu Page de base

Notes

Gestion de la configuration

  • https://www.drupal.org/node/1905070
  • $config = \Drupal::service('config.factory')->getEditable('monmodule.settings');
  • $config->set('name', value)->save(); pour définir
  • $config = \Drupal::config('monmodule.settings');
  • $config->get('name'); pour récupérer
  • $config = \Drupal::service('config.factory')->getEditable('monmodule.settings');
  • $config->clear('name')->save(); pour supprimer une valeur
  • $config = \Drupal::service('config.factory')->getEditable('monmodule.settings')->delete();

Documentation de l'API

Notes

TP : Form API

  • A la soumission enregistrer les valeurs dans une variable persistante 'content_types' de l'objet premium.settings
  • Attention, il faut désinstaller et réinstaller votre module pour que Drupal connaisse votre configuration si vous déclarez un fichier dans config/install

Notes

TP : Form API

  • Cocher les checkbox par défaut lorsque le type de contenu est activé (en lisant depuis la configuration)

Notes

Formulaire de configuration

  • Utiliser ConfigFormBase

Notes

Une autocomplétion rapide

$form['my_key'] = [
  '#type' => 'entity_autocomplete',
  '#target_type' => 'user',
];

Notes

En bonus, Manipuler les "Form Modes"

$this->entityFormBuilder()->getForm($form_id, 'form_mode');

Notes

Fournir un style par défaut

Dans un fichier de configuration '/config/install/image.style.mon_style.yml' (l'exporter depuis l'interface)

status: true
dependencies: {  }
name: mon_style
label: 'mon style'
effects:
  mon_style_desaturate:
    uuid: mon_style_desaturate
    id: image_desaturate
    weight: 1
    data: {  }

Notes

TP: Créer un style d'image "premium"

Celui-ci nous servira pour les images qui seront sur les articles premium

Notes

Le stockage

Notes

Schema API (~ méthode Drupal 7)

  • Gère la base de données
  • Se situe dans le fichier .install
  • hook_schema() -> crée une ou plusieurs tables
  • hook_schema_alter() déclare une modification (mais ne la réalise pas)
  • API de la structure
  • Fonctions de l'API
  • Les hook_update_N() servent à réaliser des actions sur la structure ou les données
  • Très utile pour les mises à jour en production, et le test de celles-ci

Notes

Génération d'entité

  • Recommandation : utiliser la console pour générer l'entité
  • drupal generate:entity:content (ou drupal generate:entity:config)
  • /!\ Attention /!\ : drush entity-updates / drupal update:entities à passer après la génération d'une entité pour mettre à jour le schéma de base de données

Notes

TP: Enregistrer les statuts

Déclarer une table premium

Via l'API, déclarer une table avec deux colonnes, nid et status. Installer cette table via un hook_update_N() ou réinstaller le module.

Altérer le formulaire de noeuds

  • Ajouter la checkbox au dessus du titre

  • Enregistrer la valeur dans notre table à l'enregistrement

  • La mettre à sa valeur par défaut à l'affichage du formulaire

Rappels 

  • hook_form_alter(&$form, $form_state, $form_id)
  • Ajouter un callback de submit...
  • Debug : module Devel + dpm($var)

Notes

TP : Contrôler l'accès aux nœuds

Utiliser l'API Node Access (documentation)

Déclarer le hook_node_access() et ne retourner AccessResult::forbidden() uniquement si les 4 conditions sont réunies :

  1. Je suis en train de voir le nœud
  2. Son type a la fonctionnalité premium activé
  3. C'est un contenu premium
  4. Je n'ai pas la permission de voir les contenu premium

Attention: ne fonctionnera pas pour les listes (dont la page d'accueil) pour des raisons de performances, il faudrait declarer hook_node_grants(). Rester simple et ne controler l'accès qu'à un nœud complet.

De même hook_node_access() n'est pas appelé pour le superadmin.

Notes

Envoi de mails

// Déclaration du contenu du mail avec hook_mail().
function monmodule_mail($key, &$message, $params) {
  $account = $params['account'];
  $node = $params['node'];
  $message['subject'] = "Bonjour " . format_username($account);
  $message['body'][] = "Le noeud {$node->title} a été créé";
  $message['body'][] = "A bientot";
}

// Envoi du mail.
$mailManager = \Drupal::service('plugin.manager.mail');
$mailManager->mail('monmodule', 'my_key', 'toto@example.com', 'fr-FR', array(
  'node' => node_load(123),
  'account' => user_load_by_name('toto'),
);

On peut utiliser la notion de tokens (jetons). 'body' est un tableau de lignes.

La clé permet à un module de gérer plusieurs types de mail.

Notes

TP : Affichage du statut premium sur le nœud

  • Trouver un hook qui permettrait d'ajouter un message <strong>Contenu premium</strong> sur chaque contenu premium
  • L'implementer avec #markup

Notes

Le thème

Notes

La partie thème d'un module

Le module fournit toujours le markup par défaut.

Le hook_theme() définit des hooks/clés qui pourront ensuite être utilisés via la propriété #theme des render arrays. Ces theme hooks généreront ensuite le markup HTML via un template.

On peut fournir des variables à ces theme hooks. Des fonctions preprocess peuvent ajouter ou modifier des variables, et également ajouter des suggestions de templates.

Notes

Déclaration et appel

function forum_theme() {
    return array(
        'forums' => array(
            'template' => 'forums',
            'variables' => array(
                'forums' => NULL,
                'topics' => NULL,
            ),
        ),
    );
}

// Mais toujours privilégier les render arrays, car altérables
$build['forums'] = array(
  '#theme' => 'forums',
  '#forums' => $forums,
  '#topics' => $topics,
);

Notes

Fonctions de thème

Exemples d'utilisations de theme() :

  • table
  • item_list
  • pager
  • links
  • image

Liste complete des implementation de theme du cœur

$image = array(
    '#theme'  => 'image',
    '#path' => drupal_get_path('module', 'monmodule') . '/monimage.png',
);
print drupal_render($image); // Quasi-automatiquement appelé par les hooks

https://www.drupal.org/developing/api/8/render/pipeline#html-main-content-renderer-pipeline

Notes

Implémentation du hook_theme()

  • Fonction template_preprocess_forums()
  • fichier "templates/forums.html.twig"

Notes

Twig

  • Un template engine pour PHP
  • Créé par Fabien Potencier (Symfony)
  • Documentation
  • Très sécurisé
  • Extensible

Notes

Exemple de syntaxe

<!DOCTYPE html>
<html lang="{{ language.language }}" dir="{{ language.dir }}">
  <head>
    <meta charset="utf-8">
    {{ head }}
    <title>{{ head_title }}</title>
    {{ styles }}
  </head>
  <body class="{{ classes }}" {{ attributes }}>
    {{ page_top }}
    {{ page }}
    {{ scripts }}
    {{ page_bottom }}
  </body>
</html>

Notes

3 syntaxes à connaître

  • {{ afficher }}
  • {# commenter #}
  • {% "programmer" %}

Notes

Afficher

Notes

Programmer

  • {% if expr %} {% else %} {% endif %}
  • {% for item in items %} ({% else %}) {% endfor %}

Notes

Les filtres

  • {{ messages | join(', ') }}
  • {{ "now" | date('d/m/Y H:i') }}
  • {{ 'Home' | t }} (ajouté par Drupal)
  • {{ content.field_date | format_date('short') }} (ajouté par Drupal)
  • Voir core/lib/Drupal/Core/Template/TwigExtension.php pour la liste des rajouts Drupal
  • Sur toute une section : {% filter upper %} Texte {% endfilter %}

  • abs, batch, capitalize, convert_encoding, date, date_modify, default, escape, first, format, join, json_encode, keys, last, length, lower, merge, nl2br, number_format, raw, replace, reverse, round, slice, sort, split, striptags, title, trim, upper, url_encode

Notes

Les fonctions

  • dump(node)
  • {{ max(1, 3, 2) }}
  • {{ random(['pomme', 'orange', 'citron']) }}
  • + celles fournies par Drupal :
    • link()
    • path()
    • url()
    • attach_library()

Notes

Inclusion de template

  • {% include 'page.html.twig' with {'foo': 'bar', 'baz': 'bat'} %}

Notes

Héritage de template

{# Template A #}
{% block foo %}
  <p>Mon premier paragraphe</p>
{% endblock %}

{# Template B #}
{% extends 'TemplateA' %}
{% block foo %}
  {{ parent() }}
    <p>Mon deuxième paragraphe</p>
{% endblock %}

{# Rendu Template B #}
<p>Mon premier paragraphe</p>
<p>Mon deuxième paragraphe</p>

Notes

Hiérarchie des templates Drupal

Notes

Debug Twig dans Drupal

parameters:
  twig.config:
    debug: true

dans sites/default/services.yml

  • {{ dump(_context|keys) }} pour voir tout ce qui est disponible

Notes

Création de theme avec un my_theme.info.yml

name: My Theme
type: theme
version: 0.1
core: 8.x
base theme: false
regions:
  branding: "Branding"
  header: "Header"
  breadcrumb: "Breadcrumb"
  content: "Content"
  footer: "Footer"
regions_hidden:
  - sidebar_first
  - sidebar_second
libraries:
  - my_theme/global_styling

Notes

TP: Créer un template pour premium

Utiliser dans le hook_node_view() un render array #markup pour afficher

Convertir le render array utilisé pour utiliser une fonction de theme (et donc un template), en implémentant un hook_theme().

Passer en paramètre le nœud et transformer "Contenu premium" en "Titre du nœud est un contenu premium".

Ajouter une image de médaille dans ce template.

Créer un preprocess pour transformer cette image en variable.

Dans le preprocess, transformer l'image en render array, et utiliser la fonction render() dans le template.

Ajouter des suggestions pour ce template.

Notes

Ajout de JS / CSS

global-styling:
  css:
    theme:
      css/style.min.css
  js:
    js/theme.js
  dependencies:
    - core/jquery
    - core/drupal.ajax

Notes

Surcharge de bibliothèque (my_theme.info.yml)

libraries-override:
  classy/base:
    css:
      components:
        css/components/menu.css: false
  user/drupal.user: false
  # jQuery Update
  core/jquery:
    js:
      assets/vendor/jquery/jquery.min.js: js/jquery-5000.max.js

Notes

Extension de bibliothèques (my_theme.info.yml)

libraries-extend:
  user/drupal.user:
    - my_theme/user
  classy/node:
    - my_theme/node

Notes

TP: Faire flotter l'image à droite

Ajouter une classe à l'image et créer un fichier CSS:

img.premium-icon {
  float: right;
}

L'ajouter lorsqu'un nœud premium est affiché.

Notes

Pour aller plus loin avec Twig

Notes

Le javascript

  • Bonnes pratiques 
  • Jquery (inclus dans Drupal) 
  • Le « Drupal way » 

Bonnes pratiques JS

  • « Unobtrusive javascript » 
    • Surcouche 
    • Comportements dégradés 
    • Accessibilité 

Voir les modules example

Notes

Les librairies JS fournies avec le cœur

  • jQuery
  • jQuery UI
  • Backbone
  • Underscore

Notes

Les services

  • https://api.drupal.org/api/drupal/services/8.6.x
  • Les services à connaître :
    • router.admin_context (->isAdminRoute())
    • path.matcher (->isFrontPage() / ->match())
    • plugin.manager.mail (->mail())
    • title_resolver (->getTitle())
    • entity_type.manager (->getStorage())
    • ...

Notes

La File API

Les streams wrappers

Un stream est un chemin, une URI, vers un fichier interne ou externe :

  • public://
  • private://

Notes

En résumé : la vie d'une page Drupal

Notes

En résumé : la vie d'une page Drupal

Notes

Fonctionnalités avancées

Notes

On peut valider les entités

https://www.drupal.org/node/2015613

$violations = $entity->validate();
$violations->count(); // extends \IteratorAggregate
$violation->getMessage();

4 niveaux de validation

  • l'entité ($entity)
  • un champ (global) de l'entité ($entity->field_invalid)
  • une valeur d'un champ de l'entité ($entity->field[0])
  • la propriété d'une valeur d'un champ de l'entité ($entity->field[0]['invalid_value'])

Pour une validation personnalisée

  • un plugin de Constraint
  • un Validator

Notes

Le cache

http://md-systems.github.io/drupal-8-caching

$key = 'my-unique-cache-key';
if ($cache = \Drupal::cache()->get($key)) {
  $data = $cache->data;
} else {
  $data = my_slow_calculation();
  \Drupal::cache()->set($key, $data);
}

// Alternative for multiple items
\Drupal::cache()->getMultiple($keys);
\Drupal::cache()->setMultiple($items);

Notes

Le cache de rendu

  • Les clés : comment identifier ce cache
  • Les contextes : Qu'est ce qui fait varier ce cache ('language', 'user.permissions', 'user.role', 'url')
  • Les tags : à quoi est associé ce cache ('node:X', EntityInterface::getCacheTags(), 'config.system.performance')
  • La durée de conservation (si on veut la forcer), avec 2 valeurs spéciales :
    • 0 (ne pas conserver en cache)
    • Cache::PERMANENT (ne pas expirer selon le temps, uniquement selon les tags)

Comment l'implémenter

$build['#cache'] = array(
  'keys' => 'my_cache',
  'contexts' => 'url',
  'tags' => array(),
  'max-age' => Cache::PERMANENT,
);

Directement dans le render array

Notes

Liste des contextes

https://www.drupal.org/developing/api/8/cache/contexts

  • cookies
  • headers
  • ip
  • languages
  • request_format
  • route
  • session
  • theme
  • timezone
  • url (url.query_args, url.path, url.host, ...)
  • user (user.roles, user.permissions, ...)

3 contextes par défaut sur tous les éléments (dans le services.yml) :

parameters:
  renderer.config:
    required_cache_contexts: ['languages:language_interface', 'theme',
    'user.permissions']

Notes

La sécurité

  • "Valider les entrées, filtrer les sorties"

Concrètement

  • URL : Html::escape(UrlHelper::stripDangerousProtocols($uri));
  • Texte brut : Html::escape($string);
  • Texte riche : check_markup($text, $format_id = NULL, $langcode = '', $filter_types_to_skip = array());
  • HTML : Xss::filter($string, array $html_tags = NULL);
  • Sinon, on considère que le texte est validé

Un bon article de blog sur le sujet

En bonus, déplacez les fichiers

Notes

Les Web Services

Appel (avec Guzzle)

// GET
$request = \Drupal::httpClient()->get($url, [
  'auth' => ['username','password']
]);
$status = $request->getStatusCode();
$response = $request->getBody();
// POST
$request = $client->post($url, [
  'json' => [
    'id'=> 'data-explorer'
  ]
]);
$response = json_decode($request->getBody());

Notes

Les Web Services

Réponse (dans un Controller)

return new JsonResponse($values);

Notes

Utilisation des APIs de Web Services du cœur

$output = $this->serializer->serialize($entity, 'json');
$entity = $this->serializer->deserialize($output,
\Drupal\node\Entity\Node::class, 'json');

https://www.drupal.org/developing/api/8/serialization

https://www.drupal.org/node/1899288

Notes

Intégrer des données à Views

Des entités

  • C'est (presque) natif !
  • Drupal console le génère pour vous.

Des tables "legacy"

Notes

Modifier des données de Views

Les données elles-mêmes

  • hook_views_data_alter()

Leur affichage

  • Views utilise des Plugins :
    • Field
    • Sort
    • Filter
    • ...

Notes

Les migrations : le module Migrate

Depuis Drupal

Depuis autre chose

  • CSV, XML, JSON supportés par des plugins
  • Une migration est un fichier YAML
  • On ne code que si on a besoin de faire des traitements spécifiques
  • Exemple détaillé
  • Exemple SQL
  • /!\ Support des traductions dans la 8.2.x /!\

Notes

Exemple de migration

id: basics
label: Import articles
migration_groups:
  - mon_groupe
source:
  plugin: csv
  path: 'public://data.csv'
  header_row_count: 1
  keys:
    - Id

process:
  title: title
  body: body
  type:
    plugin: default_value
    default_value: article

destination:
  plugin: entity:node

Notes

Quelques modules additionnels à utiliser

  • Migrate Plus : quelques fonctionnalités additionnelles pour Migrate
  • Migrate Tools : commandes Drush supplémentaires (pour lancer des migrations)
  • Migrate Source CSV : pour lire des fichiers CSV

Notes

Les plugins de traitement

  • get() : plugin par défaut
  • default_value : mettre une valeur
  • callback : appeler une fonction
  • concat, flatten, extract : quelques traitements simples de données
  • skip_on_empty : permet de sauter une ligne de l'import si la valeur est vide
  • skip_row_if_not_set : permet de sauter une ligne si la valeur n'est pas définie
  • entity_lookup : rechercher une entité (par Migrate Plus)
  • entity_generate : créer une entité (par Migrate Plus)
  • Écrivez le vôtre, c'est un plugin (MigrateProcessPlugin)

Notes

Industrialisation

  • Les profils d'installation et les distributions
  • Le packaging du site
    • Drush make
    • composer
  • La gestion de la configuration
    • CMI
    • Features

Notes

Les profils d'installation

Qu'est ce que c'est

  • Simplement une pré-configuration de Drupal.
  • Peut contenir vos propres modules, et votre propre configuration !

Quelques exemples de distribution Drupal 8

Notes

Les installeurs

Utilisation de Composer

  • Et notamment de Drupal Project
  • composer create-project drupal-composer/drupal-project
  • composer require drupal/devel:8.*
  • Solution désormais à privilégier pour Drupal 8
  • /!\ Attention aux composer update /!\

Notes

La gestion de la configuration

Configuration Management Initiative (CMI)

  • Nouveauté Drupal 8 : tout ce qui n'est pas du contenu est exportable indépendamment du reste du site

Problème

  • Actuellement, on ne peut le réimporter que sur une même "version" du site (un site dont l'installation s'est faite à partir d'une copie de la base de données du site)
  • En cours de résolution

Notes

Le worfklow qui fonctionne aujourd'hui

  • Installer un site (peu importe le moyen, profil, dump, ...)
  • Exporter toute la configuration du site (drush cex)
  • Ensuite, chaque nouveau développeur devra faire :
    • Installer le site en utilisant l'installation depuis une config. existante
    • Utiliser un workflow classique d'export / import (drush cex / cim)
  • Il reste des problèmes :
    • Export d'une configuration non souhaitée (devel, ...)
    • Utiliser "Configuration Split"
    • Voir le blog de la société Nuvole

Notes

L'avenir

Notes

Se préparer à Drupal 9

  • Suppression du code déprécié
  • composer require mglaman/drupal-check --dev
  • ./vendor/bin/drupal-check [directory]

Notes

Pour aller plus loin

  • Outils d'analyse statique
    • composer require mglaman/phpstan-drupal --dev
    • composer require vimeo/psalm --dev

Notes

Questions ?

Notes

Merci

Notes