manniru
10/17/2015 - 2:50 PM

drupal d8

drupal d8

parameters:
  session.storage.options:
    cookie_domain: '.nerocro.ows'
<?php

// Base de données.
$databases['default']['default'] = [
  'host' => 'localhost',
  'driver' => 'mysql',
  'database' => '[DATABASE]',
  'username' => '[USERNAME]',
  'password' => '[PASSWORD]',
];

// URL en laquelle on a confiance.
$settings['trusted_host_patterns'][] = '^[XXX]\.nerocro\.fr$';

// Dossier des fichiers privés.
$settings['file_private_path'] = DRUPAL_ROOT . '/../drupal_private_files';

// Ne plus avoir de cache de rendu.
$settings['container_yamls'][] = __DIR__ . '/inc/development.services.yml';
$settings['cache']['bins']['render'] = 'cache.backend.null';
$settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.null';

// Pas de cache.
$config['system.performance']['cache']['page']['max_age'] = 0;

// Partage de session.
// $settings['container_yamls'][] = __DIR__ . '/inc/cookie.services.yml';

// Désactivation du cron.
$config['automated_cron.settings']['interval'] = 0;

// Afficher les erreurs.
$config['system.logging']['error_level'] = 'verbose';

// E-mail administrateur.
$config['system.site']['mail'] = 'nerocro@XXX.fr';

// Pas d'agrégation des fichiers statiques.
$config['system.performance']['css']['preprocess'] = FALSE;
$config['system.performance']['js']['preprocess'] = FALSE;

// Config split.
$config['config_split.config_split.devel']['status'] = TRUE;
$config['config_split.config_split.stage_file_proxy']['status'] = TRUE;
$config['config_split.config_split.webprofiler']['status'] = FALSE;

// JSON API.
$settings['campus_jsonapi_remotes'] = ['127.0.0.1'];

// Solr.
$config['search_api.server.solr_5']['backend_config']['connector_config']['host'] = 'localhost';
$config['search_api.server.solr_5']['backend_config']['connector_config']['port'] = '8983';
$config['search_api.server.solr_5']['backend_config']['connector_config']['core'] = 'CORENAME';
// $config['search_api.server.solr_5']['backend_config']['connector_config']['auth']['username'] = '';
// $config['search_api.server.solr_5']['backend_config']['connector_config']['auth']['password'] = '';
// Évite de modifier la production si je lance une indexation en local.
$config['search_api.index.default']['read_only'] = TRUE;

// Thème admin.
// $config['system.theme']['admin'] = 'seven';
$config['node.settings']['use_admin_theme'] = TRUE;

// Views.
$config['views.settings']['ui']['show']['master_display'] = TRUE;
$config['views.settings']['ui']['show']['advanced_column'] = TRUE;
$config['views.settings']['ui']['show']['display_embed'] = TRUE;
$config['views.settings']['ui']['show']['preview_information'] = TRUE;
$config['views.settings']['ui']['show']['performance_statistics'] = FALSE;
$config['views.settings']['ui']['show']['additional_queries'] = TRUE;
$config['views.settings']['ui']['show']['sql_query']['where'] = 'below';
$config['views.settings']['ui']['show']['sql_query']['enabled'] = TRUE;
$config['views.settings']['ui']['always_live_preview'] = TRUE;
$config['views.settings']['ui']['exposed_filter_any_label'] = 'new_any';

Builder Doc

API Doc

Routing

Cheat sheet

Url::fromUri('mailto:contact@ows.fr')
Url::fromRoute('entity.node.canonical', ['node' => 12])
Url::fromUri('https://aider.curie.fr/don/~mon-don/')
Url::fromUri('base://sites/default/files/2016-04/xxx.pdf')->toString()
file_create_url(public://2016-05/20140303_V2.pdf)
Url::fromRoute('<current>')->toString() // URL en cours
\Drupal::request()->getUri() // URL en cours absolue
Url::fromUri(\Drupal::request()->getUri(), ['query' => ['display' => 'map']]) // Route en cour en ajoutant un paramètre
Url::fromRoute('<front>')->toString() // URL front
Url::fromRoute('<none>', NULL, ['fragment' => 'content']) // Pas de lien, juste une ancre
\Drupal::service('path.matcher')->isFrontPage(); // Est-ce la home ?
\Drupal::service('path.current')->getPath(); // La page actuelle ? (sans la langue)
\Drupal::routeMatch()->getRouteName(); // La route en cours ?
\Drupal::service('router.admin_context')->isAdminRoute($route); // Route admin ?
\Drupal::theme()->getActiveTheme()->getPath(); // Chemin du thème
\Drupal::routeMatch()->getParameter('node'); // Node en cours
// route:<none> dans un menu pour ne pas avoir de lien
// Redirection.
$response = new RedirectResponse(Url::fromRoute('view.recherche.recherche', $route_parameters)->toString());
$response->send();
// Construction d'une route : view.recherche.recherche : [nom_module].[id_vue].[id_display]
// A la place de l().
Link::createFromRoute($this->t('[TEXT]'), 'entity.view.edit_display_form', ['view' => $view_id, 'display_id' => $view_display_id], ['query' => ['destination' => \Drupal::service('path.current')->getPath()]])->toString()
// Avoir une URL propre à partir d'une string
$label = \Drupal::service('pathauto.alias_cleaner')->cleanString($label);

https://cryptic.zone/blog/drupal-8-cheatsheet-developers

Entity

Cheat sheet

$entity->entity; // Pour avoir l'entité enfant directement, si elle existe
$entity->field_XXX; // Pour avoir le field directement
// En fait, tout ce qui est dans values et properties est accessible directement (grâce à __get())
$entity->id(); // Son ID.
$entity->uuid(); // Son UUID.
$entity->getTitle(); // Son titre.
$entity->label(); // Le titre pour un terme plutôt.
$entity->toLink('...'); // Son lien.
$entity->toLink('...')->toRenderable(); // Pour éviter d'utiliser le thème link.
$entity->getEntityType()->id(); // Le nom machine du type de l'entité.
$entity->bundle(); // Son bundle.
$entity->isEmpty(); // Voir si le champ est rempli.
$entity->created->getString(); // La valeur d'un champ.
$entity->field_text->value; // La valeur d'un champ dans le cas d'un texte.
$entity->field_XXX->view([
  'label' => 'hidden',
  'type' => 'entity_reference_entity_view',
  'settings' => ['view_mode' => 'cours_visuel'],
]); // Vue d'un champ.
$entity->getFileUri(); // L'URI d'un File. toLink ne fonctionne pas car cette classe n'a pas de canonical dans son annotation.
// Date.
$entity->field_date[0]->value // Date type 31/05/2017.
foreach ($entity->field_date[0] as $value) {
  $value->getDateTime()->format('d m Y'); // Pour formater comme on veut.
  $value->getDateTime()->getTimestamp(); // Pour avoir le timestamp.
}

Afficher des entités

Traduire une entité pour afficher un champ dans la langue courante

Créer une entité custom

Supprimer une entité custom

Database::getConnection()->schema()->dropTable('id_entity');

Créer un mode d'affichage

$target_entity_type = 'media';
$labels = ['Grand carré', 'Petit carré'];
foreach ($labels as $label) {
  $machine_name = strtolower($label);
  $machine_name = \Drupal::transliteration()->transliterate($machine_name, 'fr');
  $machine_name = preg_replace('/[^a-z0-9_]+/', '_', $machine_name);
  \Drupal\Core\Entity\Entity\EntityViewMode::create(['targetEntityType' => $target_entity_type, 'id' => $target_entity_type . '.' . $machine_name, 'label' => $label])->save();
}

Créer un style d'image

\Drupal\image\Entity\ImageStyle::create(['label' => 'XXX'])->save();

Afficher/Créer une image adaptive

Champ type liste / référence d'entité

$field_XXX = $node->get('field_XXX')->getIterator()->current();
// Récupérer la clé sélectionnée.
$value = $node->field_XXX->target_id / value
// Récupérer la valeur sélectionnée.
$field_XXX->getPossibleOptions()[$value]
$field_XXX->getDataDefinition()->getLabel()

Supprimer un champ

// Ne pas oublier de purger.
field_purge_batch(10);

Ajouter un cache

// Si on veut contextualiser par la route.
// Faire une recherche dans le core pour avoir d'autres exemples de contexte.
$entity->addCacheContexts(['route']);

Tips

  • Toutes les fonctions commençant par entity_XXX sont dépréciées !
  • Tout est entité et pour savoir qui elles sont, il suffit de regarder dans /config/sync et de conditionner sur chaque ligne dans le YAML en question. D'ailleurs, le machine name est considéré comme l'ID de l'entité (peut être utilisé dans un load() par exemple).

Taxonomy

Cheat sheet

// Pour connaître le parent.
$term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
// Charge tous les termes d'un vocabulaire.
$tree = $this->entityTypeManager->getStorage('taxonomy_term')->loadTree('thematique');
// Pareil mais charge les entités.
$tree = $this->entityTypeManager->getStorage('taxonomy_term')->loadTree('categorie', 0, NULL, TRUE);

Avoir le parent d'un terme (en attente de validation de ce patch)

User

Permission

\Drupal::currentUser()->hasPermission('configure variables site for admin');

EFQ

Cheat sheet

AND/OR

$query = $this->entityTypeManager->getStorage('node')->getQuery()
  ->condition('status', NodeInterface::PUBLISHED);

$actu = $query->andConditionGroup()
  ->condition('type', 'XXX')
  ->condition('created', [XXX], 'BETWEEN');

$filter = $query->orConditionGroup();
$filter->condition($actu);
$filter->condition('field_XXX', [gmdate('Y-m', $past_month), gmdate('Y-m', $future_month)], 'BETWEEN');

$query->condition($filter);

return $query->execute();

https://kgaut.net/blog/2017/drupal-8-les-entityquery-par-lexemple.html

Tips

  • Pour afficher la requête, faire exprès de faire une erreur.

Block

News

  • Tout est bloc
  • Le titre est un bloc
  • On peut mettre un même bloc dans différentes régions
  • Les blocs sont traduisibles

Créer un bloc

Créer un context

Instancier un bloc dans le cas où il ne serait pas dans une région

/** @var \Drupal\Core\Block\BlockManagerInterface $block_manager */
$block_manager = \Drupal::service('plugin.manager.block');
/** @var \Drupal\Core\Block\BlockPluginInterface $block */
$block = $block_manager->createInstance('BLOCK_ID');
$render = $block->build();

Cheat sheet

$block = $this->entityTypeManager
  ->getViewBuilder('block')
  ->lazyBuilder('[$build['#id']]', 'full');
--------------------- best way
return [
  '#type' => 'view',
  '#name' => 'recherche',
  '#display_id' => 'tous_les_objets',
];
// Ca évite d'avoir à réellement créer une instance du block sans vraiment vouloir la placer et niveau cache c'est un p'tit peu mieux.

Date

$this->dateFormatter->format(TIMESTAMP, 'jour_et_mois'),
$this->field_date->date->format('d m'),

Datepicker natif

Avoir un élément '#type' => 'date' avec '#date_date_format' => 'd/m/Y' et le patch de Franck.

Theming

Classes pour cacher

  • invisible
  • hidden
  • visually-hidden

Container

Field

Html tag

Image

Item list

Link

Language

Langue en cours

Form

Créer un formulaire

Afficher un formulaire

\Drupal::formBuilder()->getForm('Drupal\XXX\Form\YYYForm');

Afficher un formulaire avec paramètres

\Drupal::formBuilder()->getForm('Drupal\XXX\Form\YYYForm', 'param1', 'param2');

Upload

Mieux vaut utiliser le type managed_file plutôt que file, il gère tout seul l'upload.

Mail

Envoyer un e-mail

Lire l'annotation de MailManagerInterface.

AJAX

Lien qui affiche la suite d'une liste

  • Sur le lien, rajouter class="use-ajax"
  • Créer une route vers un controller
  • Créer un controller
    • AjaxResponse()
    • AppendCommand()
    • ReplaceCommand()

Menu

Afficher avec node

$defaults = menu_ui_get_menu_link_defaults($node);
$menu_tree_parameters = new MenuTreeParameters();
$menu_tree_parameters->setRoot($defaults['id'])->excludeRoot();
$menu = \Drupal::menuTree()->load('', $menu_tree_parameters);
// -- Mieux :
// Lien en cours.
/** @var \Drupal\Core\Menu\MenuActiveTrail $menu_active_trail */
$menu_active_trail = \Drupal::service('menu.active_trail');
/** @var \Drupal\Core\Menu\MenuLinkInterface $current_menu */
$current_menu = $menu_active_trail->getActiveLink();
$current_id = $current_menu->getPluginId();
// Toute l'arborescence du menu du lien en cours.
$menu_tree = \Drupal::menuTree();
$menu_name = $current_menu->getMenuName();

// Je commence l'arborescence directement sur le node en cours.
$parameters = $menu_tree->getCurrentRouteMenuTreeParameters($menu_name);
$parameters->setRoot($current_menu->getPluginId())->excludeRoot();

$tree = $menu_tree->load($menu_name, $parameters);

// Transform the tree using the manipulators you want.
$manipulators = [
  ['callable' => 'menu.default_tree_manipulators:checkNodeAccess'],
  // Only show links that are accessible for the current user.
  ['callable' => 'menu.default_tree_manipulators:checkAccess'],
  // Use the default sorting of menu links.
  ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
];
$main_menu = $menu_tree->transform($tree, $manipulators);

if (!empty($defaults['entity_id'])) {
  foreach ($menu as $value) {
    $items[] = [
      '#type' => 'link',
      '#title' => $value->link->getTitle(),
      '#url' => Url::fromRoute($value->link->getRouteName(), $value->link->getRouteParameters()),
    ];
  }

  $build['cens_standard_en_savoir_plus'] = [
    '#theme' => 'item_list',
    '#items' => $items,
  ];
}

Afficher sans node

// Sans node.
$parameters = new MenuTreeParameters();
$menu = \Drupal::menuTree()->load('entree-menu-4', $parameters);

Créer des items de menu de façon programmatique

// Créer les menus au préalable.
// A mettre dans un hook_update_N
// Lancer l'import avant pour avoir les menus de créée.
$menus = [
  'footer-profils-fr' => [
    'Grand public et donateurs' => [
      ['title' => "La recherche"],
    ],
    'Patients et proches' => [
      ['title' => "Actualités"],
    ],
  ],
];

foreach ($menus as $menu_name => $menu) {
  foreach ($menu as $level_one_title => $item) {
    $first_level = MenuLinkContent::create([
      'title' => $level_one_title,
      'link' => ['uri' => 'internal:/node'],
      'menu_name' => $menu_name,
      'expanded' => TRUE,
      'weight' => $counter++,
    ]);
    $first_level->save();

    foreach ($item as $level_two) {
      MenuLinkContent::create([
        'title' => $level_two['title'],
        'link' => ['uri' => 'internal:/node'],
        'parent' => $first_level->getPluginId(),
        'menu_name' => $menu_name,
        'weight' => $counter++,
      ])->save();
    }
  }
}

Filtre

Appeler un filtre

$filter = Drupal::service('plugin.manager.filter')->getInstance('[id_plugin_filter]');
$text = $filter->prepare($text);
$text = $filter->process($text);

Titre de la page

$request = \Drupal::request();
$route_match = \Drupal::routeMatch();
$title = \Drupal::service('title_resolver')->getTitle($request, $route_match->getRouteObject());

Formatteur

S'il est lié à EntityReferenceFormatterBase alors, on utilise foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) { sinon on utilise foreach ($items as $delta => $item) { dans viewElements

Search API

Faire une requête sur un index

use Drupal\search_api_solr\SearchApiSolrException;

try {
  /** @var \Drupal\search_api\IndexInterface $index */
  $index = Index::load($index_id);
  if ($index === NULL) {
    $links = [$this->t("XXX")];
  }

  /** @var \Drupal\search_api\Query\QueryInterface $query */
  $query = $index->query();
  /** @var \Drupal\search_api\Query\ConditionGroupInterface $conditions */
  $conditions = $query->createConditionGroup('OR');
  $conditions->addCondition('title', $text, 'like');
  $conditions->addCondition('name', $text, 'like');
  $query->addConditionGroup($conditions);
  /** @var \Drupal\search_api\Query\ResultSetInterface $result */
  $result = $query->execute();
  
  if ($result->getResultCount() === 0) {
    $links = [$this->t('Pas de résultat pour cette recherche')];
  }
}
catch (SearchApiSolrException $exception) {
  watchdog_exception(__METHOD__, $exception);
}

hook_search_api_solr_query_alter pour ajouter une option

$query->setOption('solr_param_mm', '75%');

hook_search_api_solr_search_results_alter pour voir le retour Solr avec $results

Ajouter un filtre

$solarium_query->addFilterQuery([
  'key' => 'drm_dates_evenement',
  'query' => 'drm_dates_evenement:' . gmdate('Y-m-d'),
]);

Ajouter des filtres

$route_parameters["filters_X"] = $solarium_query->createFilterQuery(['key' => "filters_X"])->setQuery("filters_X:$profileid");
$solarium_query->addFilterQueries($route_parameters);

Ajouter un tri

$solarium_query->addSort('its_field_statistic', 'DESC');

Récupérer le nombre de résultat

// service : $container->get('search_api.query_helper')
$results = $this->QueryHelper->getAllResults();
if (current($results)->getResultCount() === 0) {
  return [];
}

Rendre un champ custom en single (car par défaut, il est en multiple)

/**
 * Implements hook_search_api_solr_field_mapping_alter().
 */
function XXX_search_api_solr_field_mapping_alter(IndexInterface $index, array &$fields) {
  if (isset($fields['pays'])) {
    $fields['pays'] = 'ss_pays';
  }
}

Créer un champ custom dans un document

Afficher une facette hors page de recherche

Voir les champs indexé : d($field_names); dans SearchApiSolrBackend->getDocuments()

preprocessIndexItems() (processor search_api)

// Configuration.
$this->configuration['fields']
// Le bundle.
$item->getDatasource()->getItemBundle($item->getOriginalObject())
// Drupal\Core\Entity\Plugin\DataType\EntityAdapter
$item->getOriginalObject()
// Drupal\media_entity\Entity\Media
$item->getOriginalObject()->getValue()

Search API Autocomplete

Ajouter l'autocomplete à un formulaire custom :

'#type' => 'textfield',
'#title' => $this->t('Votre recherche'),
'#attributes' => [
  'placeholder' => $this->t('Search'),
  'data-autocomplete-path' => '/search_api_autocomplete/recherche?display=recherche&&filter=text',
  'data-search-api-autocomplete-search' => "recherche",
  'class' => ['form-autocomplete', 'ui-autocomplete-input'],
],
'#attached' => [
  'library' => [
    'search_api_autocomplete/search_api_autocomplete',
  ],
],
'drupalSettings' => [
  'search_api_autocomplete' => [
    'recherche' => [
      'delay' => 'true',
      'auto_submit' => 'true',
      'min_length' => 3,
    ],
  ],
],

Facets

Pour savoir quelle est la/les facette(s) active(s)

Regarder facets_system_breadcrumb_alter

Snippet

/** @var \Drupal\facets\FacetSource\FacetSourcePluginManager $facet_source_manager */
$facet_source_manager = \Drupal::service('plugin.manager.facets.facet_source');
/** @var \Drupal\facets\FacetManager\DefaultFacetManager $facet_manager */
$facet_manager = \Drupal::service('facets.manager');

foreach ($facet_source_manager->getDefinitions() as $definition) {
  if ($definition['id'] === 'search_api:XXX') {
    foreach ($facet_manager->getFacetsByFacetSourceId($definition['id']) as $facet) {
      if ($facet->getFieldIdentifier() === 'field_XXX' && !empty($facet->getActiveItems()) && $facet->getActiveItems()[0] === 'XXX') {
        $solarium_query->addSort('its_field_XXX', 'ASC');
      }
    }
  }
}

Services

Procédurale et classe où on ne peut pas injecter une dépendance (=container)

Par ordre de priorité.

  • \Drupal::XXX()
    • Meilleure lisibilité
    • Typehint accessible pour IDE
  • \Drupal::service()
    • En dernier recours si \Drupal::XXX n'existe pas
    • Pour trouver le nom du service, c'est marqué dans la méthode \Drupal::XXX() ou regarder xxx.services.yml

Les deux font la même chose en gros.
Les classes de type "entity type" n'ont pas d'injection de dépendance.

Classe avec déjà une injection de dépendance

  • On injecte les services via $container->get('XXX') (aka ContainerInterface)
  • Cela permet d'injecter ce que l'on veut via les tests

Cas où il ne faut pas les utiliser

  • Dans .install car tout n'est pas encore initialisé (YAML config/YAML de module)

Quelle classe à injecter pour quoi ?

  • Plugin -> ContainerFactoryPluginInterface

Les caches

  • Bloc (à mettre sur le bon renderrable array)
'#cache' => [
  'contexts' => [
    'url',
    'languages',
  ],
  'tags' => $entity->getCacheTags(), // dans le cas où le bloc est sur une entité et dépend d'elle
  'tags' => ['node_list'], // dans le cas où le bloc est dynamique par rapport à un CRUD d'un node
  'tags' => ['menu_link_content_list'], // dans le cas où le bloc est dynamique par rapport à un CRUD d'un menu
],
  • Pour les extrafields, pas besoin grâce à EntityViewBuilder::getBuildDefaults qui en génère par défaut sauf dans le cas d'une contextualisation type url, cookie...

Twig

Composer

  • composer update : met à jour tous les paquets du composer.json
  • composer outdated --direct : affiche juste les modules contrib obsolètes
  • composer update drupal/media_entity_document : met à jour juste ce module
  • composer search [cequetuveux] : pour trouver un paquet rapidement
  • composer validate : pour voir si notre composer.json est correct
  • composer update drupal/search_api_solr --with-dependencies solarium/solarium

Console

  • ../vendor/bin/drupal list
  • ../vendor/bin/drupal generate:module --module="[X]" --machine-name="[X]" --module-path="/modules/custom" --description="[X]" --package="[X]" --core=8.x -n
  • ../vendor/bin/drupal generate:plugin:block --module="[X]" --class="[X]Block" --label="[X] : [X]" --plugin-id="[X]" --theme-region="[X]" -n
    • ATTENTION : il faut modifier le yml pour avoir un ID
  • ../vendor/bin/drupal generate:controller --module="[X]" --class="[X]" --routes='"title":"[X]", "name":"[X]", "method":"[X]", "path":"/[X]/[Y]"' --services="[X]" -n
  • ../vendor/bin/drupal generate:form --module="[X]" --class="[X]Form" --form-id="[X]" -n
    • Supprimer la route si pas besoin
  • ../vendor/bin/drupal generate:service --module="[X]" --class="[X]" --name="[MODULE_NAME].[X]" --path-service="/modules/custom/[MODULE_NAME]/src/" -n
    • Supprimer les arguments dans la route si pas besoin
  • composer up --with-dependencies drupal/console pour le mettre à jour en rc12 car drush bloque les autres versions dû à une dépendance avec symfony-console-completion, qui dépend de alchemy/zippy en 0.3 alors que console-core veur zippy 0.4

Les commandes de génération se trouvent dans src/Generator et src/Command/Generate.

Drush

  • dis => pm-uninstall
  • cc all => cache-rebuild
  • drush config-get --include-overridden system.performance cache.page.max_age
  • drush cex pour exporter
  • drush cim pour importer (équivalent de drush updb && dfra)
  • drush cim --preview=diff (pour voir les diff avant d'importer)
  • drush config-merge ... (pour voir les différences entre la config locale et celle pullée)

Install

Vrac

  • uuidgen pour générer un UUID quand on override une configuration
  • taxonomy_term_load(1)->getFieldDefinitions()['name']->getConfig('foobar') pour savoir quoi mettre dans un override yml
  • Pour afficher un kint() dans /batch, faire un kint() dans batchPage()
  • Changer la version d'un hook_update_n : SELECT * FROM key_value WHERE collection LIKE 'system.schema' and name='facets'
  • Variables
  • .info
  • On ne peut pas faire d'inline, le mettre dans un .js