manniru
3/12/2017 - 12:03 AM

drupal 8

drupal 8

<?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;

// 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;

// 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;

// Stage file proxy.
$config['stage_file_proxy.settings']['origin'] = '[URL_DEV]';

// 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

URI et Route

Pense bête

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::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]

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

Entity

Pense bête

$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.
}

Voir toutes les fonctions liées à Entity : create, delete, view, get_bundles...

Créer une entité custom

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();

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()

Suppression de champ

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

Ajout d'un cache

// Si on veut contextualiser par la route.
// Faire une recherche dans le core pour avoir d'autres exemples de contexte.
$mon_entite->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).

Date

\Drupal::service('date.formatter')->format(TIMESTAMP, 'jour_et_mois'),
$this->field_date->date->format('d m'),

EFQ

Pense bête

$nids = \Drupal::entityQuery('node')
  ->condition('type', 'publication')
  ->condition('status', NODE_PUBLISHED)
  ->condition('promote', NODE_PROMOTED)
  ->condition('field_public.entity.tid', $tids, 'IN')
  ->condition('field_articles.target_id', $nid)
  ->condition('field_date_debut_fin.value', gmdate('Y-m-d'), '<=')
  ->sort('created', 'DESC')
  ->range(0, 10)
  ->pager(5)
  ->execute();

if (!empty($nids)) {
  $nodes = Node::loadMultiple($nids);
}

Tips

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

Node

Afficher des nodes

$entities = Node::loadMultiple($ids);
// ou
$entities = \Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple($ids);

/** @var \Drupal\Core\Entity\EntityViewBuilderInterface $entity_type_manager */
$entity_type_manager = \Drupal::entityTypeManager()->getViewBuilder('node');
$items = $entity_type_manager->viewMultiple($entities, $view_mode);

// Old way.
/** @var \Drupal\node\NodeInterface $entities */
//foreach ($entities as $entity) {
//  $items[] = $entity_type_manager->view($entity, 'teaser');
//}

Taxonomy

Pense bête

$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term'); // Pour connaître le parent
$term = Term::load($tid); // Charger un terme

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

Block

Créer un bloc

Pense bête

$block = \Drupal\block\Entity\Block::load('[BLOCK_ID]');
$search_block = \Drupal::entityTypeManager()
  ->getViewBuilder('block')
  ->view($block);
---------------------
$search_block = \Drupal::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.

Theming

Classes pour cacher

  • invisible
  • hidden
  • visually-hidden

List item

// foreach...
$items[] = [
  '#markup' => 'TOTO',
  '#wrapper_attributes' => ['class' => ['TOTO']],
];

$build['months'] = [
  '#theme' => 'item_list',
  '#items' => $items,
  '#attributes' => [
    'class' => 'actu-nav',
  ],
];

Image

'left' => [
  '#theme' => 'image',
  '#uri' => theme_get_setting('logo')['path'],
  '#width' => 80,
  '#height' => 80,
],

Lien

[
  '#type' => 'link',
  '#title' => t('Ton lien'),
  // '#title' => ['#markup' => [HTML]], // dans le cas où on veut mettre du HTML dans le lien
  '#url' => Url::fromUri('XXX'),
],

Html tag

[
  '#type' => 'html_tag',
  '#tag' => 'h2',
  '#value' => $this->t(''),
],

Form

Créer un formulaire

Afficher un formulaire

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

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.

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

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

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()

Media

https://drupal-media.gitbooks.io/drupal8-guide/content/index.html

ENTITY MEDIA DOCUMENT
Création/modification de l'entité :
create
__construct
buildConfigurationForm

création du bundle :
create
__construct
thumbnail

Instanciation du média :
providedFields : retourne le nom des différentes propriétés (tableau)
getField : retourne les différentes propriétés (mime, size...)

Upload :
defaultConfiguration : configuration par défaut à l'ajout du widget sur EB
buildConfigurationForm : ça se passe dans l'ajout du widget, sert à afficher les deux champs location/extensions
getForm : affiche le form quand on clique sur l'onglet document
submit : gestion de la soumission du form au dessus

TEST:

$e = entity_load('media', 4);
d($e->getType());
d($e->getType()->providedFields());
d($e->getType()->getField($e, 'size'));
d($e->getType()->getField($e, 'mime'));

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 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
  • Node::create(['type' => 'numero_revue']) et non pas directement une classe de type node qui hériterait de Node (Drupal est mal foutu à ce niveau là)

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 = annotation (Block, Facet...) -> 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...

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
  • ['#cache']['max-age'] = 0; dans n'importe quel renderable array pour ne pas utiliser le cache de rendu
  • Pour afficher un kint() dans /batch, faire un kint() dans batchPage()
  • Surcharger un service

Install

Composer

  • composer update : met à jour tous les paquets du composer.json
  • 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

Console

  • ../vendor/bin/drupal list
  • 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

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)
  • git rm --cached modules/contrib/taxonomy_manager && git add modules/contrib/taxonomy_manager (pour enlever le .git)

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

Annexes