Занурюємось у світ створення модулів! Стаття для
Drupal-розробників від Drupal-розробника InternetDevels.
У цій статті ми розглянемо процес створення модуля під Drupal 8. А саме: ми створимо дві сторінки, одна з яких додаватиме дані в "свою" табличку, а друга — відображатиме її вміст в потрібному для нас вигляді. Отже, почнемо.
Налаштування середовища
Перше, що я б порадив зробити, — це налаштувати оточення для простоти написання коду. Я використовую phpstorm, тож ось посилання для його налаштування: https://confluence.jetbrains.com/display/PhpStorm/Drupal+Development+using+PhpStorm
Створення директорії для модуля
Після налаштування середовища нам потрібно створити папку для модуля по шляху /modules/custom/mypage (в Drupal 7 модулі лежать /sites/all/modules).
Створення .info файлу
Далі нам потрібно придумати назву модуля і створити файл для опису модуля. Я назвав mypage, отже, файл назвемо mypage.info.yml (в Drupal 7 це mypage.info).
Ось що у мене вийшло:
# Назва модуля. name: My Page # Детальний опис модуля. description: Create page in drupal 8 # Вказуємо, в якій групі модулів відображатиметься наш модуль на сторінці /admin/modules. package: Custom # Вказуємо, що це модуль. Буває(module, theme, profile). type: module # Ключ, що визначає версію ядра. Обов’язковий!!! core: 8.x # Тут вказується назва роута (який оголошується в файлі mypage.routing.yml) для конфігурації модуля. configure: mypage.add_record # Також можуть бути наступні значення: # dependencies - список модулів, від яких залежить ваш модуль. # test_dependencies - список модулів, які будуть включатися при виконанні автоматизованих тестів. # hidden: TRUE - опція приховує цей модуль із списку модулів. # Щоб показати всі приховані модулі в списку, потрібно прописати $settings['extension_discovery_scan_tests'] = TRUE в settings.php.
Детальний опис усіх властивостей можна подивитися тут: https://www.drupal.org/node/2000204
Створення файлу .module
В Drupal 7 файл .module був обов'язковим, а в Drupal 8 його може і не бути. У цьому файлі я створюю свою тему для потрібного нам відображення даних.
<?php /** * Implements hook_theme(). */ function mypage_theme() { return array( 'mypage_theme' => array( // Назва теми 'variables' => array( // Масив назви змінних 'data' => array(), ), 'templates' => 'templates/mypage-theme', // Шлях до шаблону без .html.twig, в якому і будуть доступні змінні, що вказані вище ), ); }
В Drupal 8, на відміну від Drupal 7, не можна створювати тему, яка віддаватиме html — все лежить у шаблоні TWIG.
Створення шаблону TWIG
В Drupal 8 змінився шаблонізатор. Замість звичного нам PHPtemplate використовується TWIG. Всі відмінності та інформацію про те, як з ним працювати, можна знайти тут: https://www.drupal.org/theme-guide/8/twig
Оскільки в темі ми вказували шлях 'templates/mypage-theme', відповідно, в корені папки модуля створюємо папочку templates і в ній створюємо файлик mypage-theme.html.twig, в якому, власне, і лежатиме наш HTML.
<div class="row column text-center"> <h2>{{ data.title }}</h2> <hr> <p>{{ data.body }}</p> </div>
Тут якраз видно роботу зі змінною data, яку вказували в hook_theme ().
Створення src дерикторії
Далі нам потрібно створити підкаталог в папці нашого модуля, в якому ми будемо зберігати контролери, плагіни, форми, шаблони і тести. Цей підкаталог повинен називатися src. Це дозволить класу контролера додатися в автозавантаження (PSR-4 http://www.php-fig.org/psr/psr-4/) автоматично, відповідно, вручну підключати нічого не потрібно.
Створення таблиці в БД
Створення таблиць ніяк не змінилося — в Drupal 8 все той же hook_shema(). Всі типи для таблиць можна подивитися тут https://www.drupal.org/node/159605
Власне, створюється файл в корені модуля mypage.install, який відповідає за дії при встановленні або update.
Для тесту я створив табличку з 3 полями, в які буду записувати дані з конфіг форми, про яку напишу нижче.
<?php /** * @file * Install, update and uninstall functions for the mypage module. */ /** * Implements hook_schema(). */ function mypage_schema() { $schema['mypage'] = array( 'description' => 'Custom mypage table.', 'fields' => array( 'id' => array( 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, ), 'title' => array( 'description' => 'Title page', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', ), 'body' => array( 'description' => 'Body page', 'type' => 'text', 'not null' => TRUE, 'size' => 'big', ), ), 'primary key' => array('id'), ); return $schema;
Створення сервісу
У ранніх версіях Друпала при рендері сторінки ініціалізувалися всі функції і методи незалежно від того, використовуємо ми їх чи ні. Це впливало на швидкість роботи програми. Так ось, в Друпал 8 з'явилося таке поняття, як services. Воно запозичене у Symphony і дозволяє викликати ті функції, які ми використовуємо при написанні коду.
Для створення сервісу потрібно створити файл з ім'ям mypage.services.yml, в якому і опишемо власне сам сервіс.
services: // Назва сервісу. mypage.db_logic: // Клас, який повертає сервіс. // Оскільки в Drupal 8 використовується автозавантажувач PSR-4, то src пропускаємо. class: Drupal\mypage\MyPageDbLogic // Аргументи, які прилетять в в конструктор класу. arguments: ['@database'] // Детальніше пояснення: https://www.drupal.org/node/2239393. tags: - { name: backend_overridable }
Детальний опис можна подивитися тут https://www.drupal.org/node/2133171
Цей сервіс потрібен нам для роботи з таблицею, яку створили вище. Аргумент @database дає можливість працювати з БД.
Виклик сервісу в коді
$db_logic = \Drupal::service('mypage.db_logic');
Клас для сервісу
<?php namespace Drupal\mypage; use Drupal\Core\Database\Connection; /** * Defines a storage handler class that handles the node grants system. * * This is used to build node query access. * * @ingroup mypage */ class MyPageDbLogic { /** * The database connection. * * @var \Drupal\Core\Database\Connection */ protected $database; /** * Constructs a MyPageDbLogic object. * * @param \Drupal\Core\Database\Connection $database * The database connection. */ // Змінна $database прилетіла до нас з аргументу сервісу. public function __construct(Connection $database) { $this->database = $database; } /** * Add new record in table mypage. */ public function add($title, $body) { if (empty($title) || empty($body)) { return FALSE; } // Приклад роботи з БД в Drupal 8. $query = $this->database->insert('mypage'); $query->fields(array( 'title' => $title, 'body' => $body, )); return $query->execute(); } /** * Get all records from table mypage. */ public function getAll() { return $this->getById(); } /** * Get records by id from table mypage. */ public function getById($id = NULL, $reset = FALSE) { $query = $this->database->select('mypage'); $query->fields('mypage', array('id', 'title', 'body')); if ($id) { $query->condition('id', $id); } $result = $query->execute()->fetchAll(); if (count($result)) { if ($reset) { $result = reset($result); } return $result; } return FALSE; } }
Створення роутинга
У Друпал 8 hook_menu замінили роутингами і винесли це все в окремий файл. Файл назвали mypage.routing.yml.
// Назва роутинга, його використовують для генерації посилань, редиректу і так далі. mypage.add_record: // Шлях, який буде на сайті. path: '/admin/mypage/add_record' defaults: // Заголовок сторінки _title: 'Add record' // Відображення на сторінці форми. Аналог drupal_get_form _form: '\Drupal\mypage\Form\ConfigFormMyPage' requirements: // Права _permission: 'access simple page' mypage.view: path: '/mypage/{mypage_id}' defaults: _title: 'My page' // page callback. Метод, який рендерить нашу сторінку. _controller: '\Drupal\mypage\Controller\MyPageController::content' requirements: _permission: 'view content' // Можно використовувати регулярку для коректності вмісту аргументу. // В даному випадку будуть доступні в змінній mypage_id тільки цілі числа, інакше 404. mypage_id: \d+
Створення controller
Спершу трохи теорії по MVC:
Модель (Model) — надає певну інформацію (дані та методи роботи з цими даними), реагує на запити, змінюючи свій стан. Не містить інформації, як ці знання можна візуалізувати.
Представлення (View) — відповідає за відображення інформації (візуалізацію). Часто в якості представлення виступає форма (вікно) з графічними елементами.
Контролер (Controller) — забезпечує зв'язок між користувачем і системою: контролює введення даних користувачем і використовує модель і представлення для реалізації необхідної реакції.
Створимо файл на шляху /modules/mypage/src/Controller і назвемо його MyPageController.php
<?php /** * @file * Contains \Drupal\mypage\Controller\MyPageController. */ namespace Drupal\mypage\Controller; use Drupal\Core\Controller\ControllerBase; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class MyPageController extends ControllerBase { // Назва змінної такої ж як у роутері!!! public function content($mypage_id = NULL) { // Завантаження сервісу. $db_logic = \Drupal::service('mypage.db_logic'); if ($record = $db_logic->getById($mypage_id, TRUE)) { return array( // Робота з нашою темою. '#theme' => 'mypage_theme', '#data' => $record, ); } // Поверне: сторінку не знайдено. throw new NotFoundHttpException(); } }
Робота с конфігами
На заміну variable_set/get прийшли config. Для створення змінної потрібно в /mypage/config/schema створити файл configform_mypage.schema.yml і прописати:
// Назва конфіга configform_mypage.settings: type: config_object label: 'Configform Example settings' mapping: email_address: type: string label: 'This is the example email address.'
Детальніше тут: https://www.drupal.org/node/1905070
Приклад роботи:
$config = $this->config('configform_mypage.settings'); $config->set('email_address', ‘test’); $config->save();
Створення Config Form
Створення форм в Друпал 8 кардинально змінилося — тепер немає звичних нам хуків, а є класи. Отже, створимо файл ConfigFormMyPage.php і помістимо його /modules/mypage/src/Form
<?php /** * @file * Contains \Drupal\mypage\Form\ConfigFormMyPage. */ namespace Drupal\mypage\Form; use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\Component\Utility\SafeMarkup; class ConfigFormMyPage extends ConfigFormBase { /** * {@inheritdoc}. */ // Метод, який вертає ід форми. public function getFormId() { return 'configform_mypage_form'; } /** * {@inheritdoc}. */ // Замість hook_form. public function buildForm(array $form, FormStateInterface $form_state) { $form = parent::buildForm($form, $form_state); $config = $this->config('configform_mypage.settings'); $form['email'] = array( '#type' => 'email', '#title' => $this->t('Your .com email address.'), '#default_value' => $config->get('email_address'), ); $form['title'] = array( '#type' => 'textfield', '#title' => $this->t('Title'), '#required' => TRUE, ); $form['body'] = array( '#type' => 'textarea', '#title' => $this->t('Body'), '#rows' => 5, '#required' => TRUE, ); $db_logic = \Drupal::service('mypage.db_logic'); $data = $db_logic->getAll(); if ($data) { $form['data'] = array( '#type' => 'table', '#caption' => $this->t('Table Data'), '#header' => array($this->t('id'), $this->t('Title'), $this->t('Body')), ); foreach ($data as $item) { // Приклад створення посилання. // Першим аргументом вказується назва роута, другим аргументом — його параметри: $url = Url::fromRoute('mypage.view', array( 'mypage_id' => $item->id, )); $form['data'][] = array( 'id' => array( '#type' => 'markup', '#markup' => \Drupal::l($item->id, $url), ), 'title' => array( '#type' => 'markup', '#markup' => $item->title, ), 'body' => array( '#type' => 'markup', '#markup' => $item->body, ), ); } } return $form; } /** * {@inheritdoc} */ // Замість hook_form_validate. public function validateForm(array &$form, FormStateInterface $form_state) { if (strpos($form_state->getValue('email'), '.com') === FALSE) { $form_state->setErrorByName('email', $this->t('This is not a .com email address.')); } } /** * {@inheritdoc} */ // Замість hook_form_submit. public function submitForm(array &$form, FormStateInterface $form_state) { $db_logic = \Drupal::service('mypage.db_logic'); $title = SafeMarkup::checkPlain($form_state->getValue('title')); $body = SafeMarkup::checkPlain($form_state->getValue('body')); $db_logic->add($title, $body); // На заміну variable_set/get прийшли config. // Приклад роботи з ними. $config = $this->config('configform_mypage.settings'); $config->set('email_address', $form_state->getValue('email')); $config->save(); return parent::submitForm($form, $form_state); } /** * {@inheritdoc} */ // Масив імен об'єктів конфігурації, які доступні для редагування. protected function getEditableConfigNames() { return ['configform_mypage.settings']; } }
Додавання посилання в пункт меню
Додамо посилання в admin menu — для цього нам потрібно створити файл в корені модуля mypage.links.menu.yml і там прописати наступне:
mypage.view: // Заголовок title: 'Add content for mypage' // Системна назва меню parent: system.admin // Назва роута. route_name: mypage.add_record
Детальніше можна прочитати тут https://www.drupal.org/developing/api/8/menu
У результаті повинна вийти наступна структура файлів:
На цьому все — наш модуль готовий!
Дивіться також інші блоги наших розробників про Drupal 8:
Темізація у Друпал 8 в прикладах