Долго думал продолжать ли работать с Drupal или уже спрыгнуть на какой-нибудь фреймворк, т.к. постоянные изменения API уже достали. И все таки решил пока остаться. Поэтому взялся разбираться с Drupal 8.
Начал свое изучение с парочки русскоязычных статей и с версии beta2. Спасибо этим статьям, но уже на beta2 некоторые вещи из статей не работали и приходилось подправлять.
Например вызов функции контроллера отвечающая за содержимое в примерах может встречаться типа такой:
_content: '\Drupal\allexx\Controller\Grabber::StartGrabber'
А нынче она выглядит уже вот так:
_controller: '\Drupal\allexx\Controller\Grabber::StartGrabber'
Примеров в интернете пока мало, а на русском языке и подавно, поэтому как реализовать те или иные моменты искал прямо в ядре.
Для чего я полез сразу в создание модулей. Задача у меня была такая, чтобы во-первых создать граббер. Во-вторых организовать на сайте несколько разделов товаров со своим блоком меню. Теоретически можно, конечно, было попробовать реализовать это за счет названий ссылок и на их основе выводить или не выводить блоки. Но во-первых, на данный момент еще нет модуля pathauto, а во-вторых что делать, если посетитель заходит на нейтральную по отношению к разделам страницу (контакты в частности).
Про граббер пока не буду рассказывать, пока что про вторую задачу.
Теперь к практике.
Первое с чем столкнулся при разработке модулей - это не выводится описание ошибок. Чтобы включить эту функцию идем в sites/default/settings.php
Там в конце файла добавляем строку:
$config["system.logging"]["error_level"] = "verbose";
Можно сразу добавить еще одну настройку, которая будет полезна при верстке. После изменения вашей темы по умолчанию нужно каждый раз чистить кэш. Чтобы временно отключить это кэширование редактируем файл sites/default/services.yml. Устанавливаем там значение cache: false, после этого очистить кэш и можно спокойно верстать. Подробности о настройках файла services.yml здесь.
Модуль я свой назвал allexx. Соответственно в папке modules создаем папку allexx.
У меня в этой папке сейчас файлы:
allexx.info.yml allexx.links.menu.yml allexx.module allexx.routing.yml
Мы создаем сейчас просто блоки с разделами и подразделами и для этой задачи нам понадобится лишь один файл allexx.info.yml (остальные файлы мне понадобились для создания граббера). Содержимое файла:
# Комментарии указываются в yml через хэштег. # Название модуля (отображается в списке модулей). name: Allexx # Описание модуля. Пишется исключительно на английском. description: 'Allexx module' # Объявляем что это модуль. type: module # Версия ядра для которого модуль. core: 8.x # Версия модуля. version: 1.0 # Раздел в модулях где будет располагаться наш модуль. #package: Examples
Это я просто откуда-то содрал. Нужно заявить о нашем модуле, вот этим файлом мы об этом заявили.
Дальше переходим непосредственно к разработке наших блоков. Для этого создаем папку
allexx\src\Plugin\Block
Я делал два блока. Один блок для выбора основных разделов сайта. Второй блок для вывода подразделов соответствующих выбранному основному разделу. Соответственно в папке с блоками создаем два файла:
RazdelyBlock.php MyCatalogBlock.php
Содежримое файла RazdelyBlock.php
<?php namespace Drupal\allexx\Plugin\Block; use Drupal\Core\Block\BlockBase; use Drupal\Core\Url; /** * Provides a 'Demo' block. * * @Block( * id = "razdely_block", * admin_label = @Translation("Razdely block"), * ) */ class RazdelyBlock extends BlockBase { /** * {@inheritdoc} */ public function build() { $vid = 'razdely'; $parent = 0; // Загружаем вместе с сущностями (true) $tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid,$parent,1,true); $taxonomy_list = array(); foreach ($tree as $item) { //$url = Url::fromRoute('entity.taxonomy_term.canonical',array('taxonomy_term' => $item->tid)); //$internal_link = \Drupal::l($item->name, $url); //$taxonomy_list[] = $internal_link; $taxonomy_list[] = entity_view($item,'teaser'); // Рабочий вариант } $res = array( '#theme' => 'item_list', '#items' => $taxonomy_list, ); return $res; } }
Содежримое файла MyCatalogBlock.php
<?php namespace Drupal\allexx\Plugin\Block; use Drupal\Core\Block\BlockBase; use Drupal\Core\Url; /** * Provides a 'Demo' block. * * @Block( * id = "my_catalog_block", * admin_label = @Translation("My Catalog block"), * ) */ class MyCatalogBlock extends BlockBase { /** * {@inheritdoc} */ public function build() { $vid = 'razdely'; if (!empty($_GET['cur_tid'])) { $cur_tid = $_GET['cur_tid']; } else{ $cur_tid = 0; $current_path = \Drupal::request()->attributes->get('_system_path'); $path_args = explode('/', $current_path); if (($path_args[0] == 'taxonomy') || ($path_args[0] == 'catalog')) { $cur_tid = $path_args[2]; } elseif (($path_args[0] == 'node') && (isset($path_args[1]))) { $tids = \Drupal::entityManager()->getStorage('taxonomy_term')->getNodeTerms(array($path_args[1]),array($vid)); if (sizeof($tids) > 0) { $cur_tid = key(current($tids)); } } } if ($cur_tid == 0){ if (!empty($_SESSION['razdel'])) { $top_parent = $_SESSION['razdel']; } else{ $top_parent = 1; } } else{ $parents = \Drupal::entityManager()->getStorage('taxonomy_term')->loadAllParents($cur_tid); end($parents); // move the internal pointer to the end of the array $top_parent = key($parents); } $_SESSION['razdel'] = $top_parent; $tree = \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid,$top_parent); $taxonomy_list = array(); $cur_depth = 0; foreach ($tree as $item) { $active = ''; $span = ''; $first = ''; if ($cur_tid == $item->tid) $active = ' active'; if ($item->depth == 0) $span = '<span class="list-item"></span>'; if ($cur_depth != $item->depth) { $cur_depth = $item->depth; if ($item->depth != 0) $first = ' first'; } // Если по таксономии, то по роуту подтягивается алиас //$url = Url::fromRoute('entity.taxonomy_term.canonical',array('taxonomy_term' => $item->tid)); //$internal_link = \Drupal::l($item->name, $url); if ($item->depth == 0) { $internal_link = '<span class="title">'.$item->name.'</span>'; } else { $lookup_path = \Drupal::service('path.alias_storage')->lookupPathAlias('catalog/term/'. $item->tid, 'ru'); if (empty($lookup_path)) { $lookup_path = 'catalog/term/'. $item->tid; } $internal_link = '<a href="/'.$lookup_path.'">'.$item->name.'</a>'; } //$taxonomy_list[] = $internal_link; $taxonomy_list[] = array( '#wrapper_attributes' => array('class' => array('depth-'.$item->depth.$active.$first)), '#markup' => $span.$internal_link, ); } $res = array( '#theme' => 'item_list', '#items' => $taxonomy_list, ); return $res; //return $output; } }
Я не стал убирать закомментированные строки, т.к. они пригодятся для общего случая, когда используются ссылки таксономии (taxonomy/term/tid). Я же, чтобы не трогать вывод материалов по стандартным ссылкам таксономии, сделал views с адресом catalog/tid. Этим адресам вручную назначил алиасы. Поэтому в коде мастерил ссылки, в том числе принудительно привязывая алиасы.
В конце кода тоже показал, что сейчас нельзя просто так возвращать строковое значение. Хотя еще в бета 2 я мог возвращать строку и пока тестировал мог смотреть что у меня там получается. А вот в бета 3 уже получил сообщение:
InvalidArgumentException: _content controllers are not allowed to return strings. You can return a render array, a html fragment or a response object.
И еще попутно пришлось подправить ошибку в ядре, т.к. получал вот такое сообщение:
Undefined variable: all_tids in Drupal\taxonomy\TermStorage->getNodeTerms() (line 355 of core\modules\taxonomy\src\TermStorage.php).
Исправляем так:
$results = array(); $all_tids = array(); // XA foreach ($query->execute() as $term_record) { $results[$term_record->node_nid][] = $term_record->tid; $all_tids[] = $term_record->tid; }
Хотел было описать как делал контроллер для граббера с загрузкой материалов с полями-картинками, полями-файлами(множественными), терминами таксономии и т.д. Контроллер то сделал, но описывать пока времени нет. А просто так выкладывать кучу кода вроде как не правильно.
Спасибо, вроде единственная статся в интернете, где описана реализация модуля по сложнее hello world...
Спасибо!
Отправить комментарий