- Przygotowanie środowiska i struktury modułu
- Wymagania wstępne i konfiguracja Drupala
- Lokalizacja modułu i jego podstawowa struktura
- Tworzenie pliku info.yml – podstawowa deklaracja modułu
- Konwencje nazewnicze i dobre praktyki
- Definiowanie ścieżek i kontrolerów
- Plik routing.yml – mapowanie adresów URL
- Tworzenie kontrolera w katalogu src/Controller
- Tablice renderujące i system renderowania
- Sprawdzanie działania trasy w panelu administracyjnym
- Formularze, konfiguracja i uprawnienia
- Dodawanie formularza konfiguracyjnego modułu
- Dodanie trasy do formularza i pozycji w menu
- Definiowanie uprawnień w pliku permissions.yml
- Wykorzystanie konfiguracji w logice modułu
- Integracja z API Drupala i rozbudowa modułu
- Hooki – reagowanie na zdarzenia systemowe
- Usługi (services) i kontener zależności
- Tworzenie bloków jako pluginów
- Przechowywanie danych i praca z encjami
Własny moduł w Drupal otwiera drogę do pełnego wykorzystania możliwości tego systemu – od tworzenia nowych typów zawartości, przez integracje z zewnętrznymi API, po automatyzację zadań w panelu administracyjnym. Zamiast polegać wyłącznie na gotowych rozszerzeniach, możesz zbudować rozwiązanie dopasowane dokładnie do specyfiki projektu. Poniższy poradnik przeprowadzi cię krok po kroku przez proces tworzenia prostego, ale poprawnego technicznie modułu, z naciskiem na dobre praktyki i zrozumienie mechanizmów stojących za Drupal.
Przygotowanie środowiska i struktury modułu
Wymagania wstępne i konfiguracja Drupala
Aby rozpocząć pracę nad własnym modułem, potrzebujesz działającej instalacji Drupal. Najwygodniej korzystać z lokalnego środowiska (np. XAMPP, Laragon, DDEV, Lando), które pozwala szybko odświeżać stronę i testować zmiany bez wpływu na środowisko produkcyjne. Upewnij się, że masz dostęp do:
- katalogu instalacji Drupala (aby móc tworzyć pliki modułu),
- konsoli systemowej (terminal), jeśli chcesz korzystać z narzędzi takich jak Drush,
- przeglądarki z włączonym trybem deweloperskim (podgląd błędów, nagłówków, żądań sieciowych).
Dla efektywnej pracy warto włączyć wyświetlanie błędów i logów. W Drupalu można skorzystać z modułu dblog (Wydarzenia i logi), który zapisuje informacje o błędach i ostrzeżeniach w bazie danych. Jest to szczególnie istotne na etapie tworzenia modułu, gdy każdy błąd w kodzie może zablokować część funkcjonalności strony.
Lokalizacja modułu i jego podstawowa struktura
W Drupalu moduły użytkownika umieszcza się zazwyczaj w katalogu modules/custom w głównym katalogu projektu. Jeśli folder custom nie istnieje, po prostu go utwórz. Następnie w obrębie tego katalogu stwórz nowy folder o nazwie odpowiadającej twojemu modułowi, np. my_example_module. Nazwa katalogu będzie jednocześnie nazwą techniczną modułu (tzw. machine name), więc powinna być pisana małymi literami, bez spacji, z użyciem podkreśleń zamiast spacji.
Podstawowa struktura modułu w Drupalu 8/9/10 wygląda następująco:
- modules/custom/my_example_module/my_example_module.info.yml
- modules/custom/my_example_module/my_example_module.routing.yml
- modules/custom/my_example_module/src/Controller/MyExampleController.php
- modules/custom/my_example_module/my_example_module.permissions.yml (opcjonalnie)
Nie wszystkie pliki są obowiązkowe od razu, lecz już na początku warto przyjąć czytelną organizację katalogu src, w której będziesz przechowywać klasy PHP (np. Controller, Plugin, Form). Przejrzysta struktura ułatwi utrzymanie modułu i jego rozwój.
Tworzenie pliku info.yml – podstawowa deklaracja modułu
Najważniejszym pierwszym plikiem modułu jest plik .info.yml, ponieważ to on informuje Drupal o istnieniu modułu. W katalogu modułu stwórz plik o nazwie zgodnej z nazwą katalogu, np. my_example_module.info.yml. Przykładowa zawartość:
name: My example module
type: module
description: Prosty moduł demonstracyjny dodający własną stronę.
package: Custom
core_version_requirement: ^10 || ^9 || ^8
version: 1.0.0
Pole name to nazwa modułu widoczna w panelu administracyjnym. Pole description pomaga administratorowi zrozumieć, do czego moduł służy. Package określa grupę, w której moduł zostanie wyświetlony na liście (np. Custom, Development, SEO). Core_version_requirement zapewnia, że moduł będzie dostępny tylko dla odpowiednich wersji Drupala.
Konwencje nazewnicze i dobre praktyki
Od początku warto trzymać się spójnych konwencji nazewniczych. Nazwa modułu powinna być krótka, opisowa i neutralna językowo. W kodzie PHP klasy umieszcza się w przestrzeni nazw Drupal\\nazwa_modułu, np. Drupal\my_example_module\Controller. W obrębie modułu zachowuj podział na katalogi (Controller, Plugin, Form, Service), co jest zgodne ze standardami Drupala opartymi na PSR-4.
Dbaj o czytelne komentarze w kodzie, ale unikaj nadmiarowego komentowania oczywistych operacji. W Drupalu liczy się także zgodność ze standardem kodowania (Drupal Coding Standards), co można egzekwować narzędziami takimi jak PHP_CodeSniffer z rozszerzeniem dla Drupala. Dzięki temu twój moduł będzie łatwiejszy do zrozumienia i utrzymania przez innych programistów.
Definiowanie ścieżek i kontrolerów
Plik routing.yml – mapowanie adresów URL
Kolejnym krokiem przy tworzeniu własnego modułu jest zdefiniowanie ścieżek, czyli adresów URL powiązanych z kodem modułu. Służy do tego plik .routing.yml, w którym określasz, jaki kontroler obsługuje daną ścieżkę, jakie uprawnienia są wymagane oraz jaki tytuł ma mieć strona.
Przykładowa zawartość pliku my_example_module.routing.yml może wyglądać tak:
my_example_module.page:
path: '/my-example’
defaults:
_controller: '\Drupal\my_example_module\Controller\MyExampleController::content’
_title: 'Strona z własnego modułu’
requirements:
_permission: 'access content’
Każdy wpis w pliku routing.yml to osobna trasa (route), identyfikowana unikalnym kluczem (tu: my_example_module.page). Parametr path określa adres, pod którym strona będzie dostępna. W sekcji defaults wskazujesz metodę kontrolera, która ma zwrócić zawartość strony. Requirements określa wymagane uprawnienia – w przykładzie każdy użytkownik z uprawnieniem access content będzie mógł zobaczyć stronę.
Tworzenie kontrolera w katalogu src/Controller
Aby zadeklarowana trasa działała, musisz utworzyć odpowiedni kontroler. W katalogu src utwórz folder Controller, a w nim plik MyExampleController.php. Przykładowy minimalny kontroler w Drupalu może wyglądać następująco:
<?php
namespace Drupal\my_example_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\Markup;
class MyExampleController extends ControllerBase {
public function content() {
$markup = '<p>To jest treść wygenerowana przez własny moduł Drupal.</p>’;
return [
'#type’ => 'markup’,
'#markup’ => Markup::create($markup),
];
}
}
Kontroler dziedziczy po klasie ControllerBase, co zapewnia dostęp do wielu przydatnych metod, np. do tłumaczeń czy usług kontenera. Metoda content zwraca tablicę renderującą (render array), którą Drupal przetworzy do finalnego HTML. Użycie klasy Markup pozwala bezpiecznie opakować ciąg HTML w strukturę, którą silnik renderujący Drupala rozumie i potrafi obsłużyć.
Tablice renderujące i system renderowania
W Drupalu bezpośredni echo HTML jest niewskazany. Zamiast tego stosuje się tablice renderujące, które opisują, co ma zostać wyświetlone. Przykładowa tablica:
return [
'#type’ => 'container’,
'#attributes’ => [’class’ => [’my-example-wrapper’]],
'content’ => [
'#type’ => 'markup’,
'#markup’ => Markup::create('<p>Przykładowa zawartość w kontenerze.</p>’),
],
];
System renderujący przetwarza te struktury, nakłada cache, obsługuje tłumaczenia, filtry zabezpieczające oraz możliwość modyfikacji zawartości przez inne moduły (hooki). To jedna z najważniejszych koncepcji, którą należy zrozumieć przy tworzeniu modułów, ponieważ pozwala korzystać z mechanizmów Drupala zamiast samodzielnie generować HTML.
Sprawdzanie działania trasy w panelu administracyjnym
Po utworzeniu pliku info.yml, routing.yml i kontrolera możesz przejść do panelu administracyjnego. Wejdź na stronę Rozszerzenia i odszukaj swój moduł w sekcji Package, którą ustawiłeś (np. Custom). Zaznacz moduł i włącz go. Po włączeniu przejdź na adres zdefiniowany w pliku routing.yml, np. /my-example. Jeśli wszystko zostało poprawnie skonfigurowane, zobaczysz treść zwracaną przez metodę kontrolera.
Gdy napotkasz błąd, zajrzyj do logów systemowych. Błędy w plikach YAML (np. złe wcięcia, niepoprawna składnia) są częstą przyczyną problemów. Pamiętaj, że pliki YAML są wrażliwe na wcięcia i spacje – używaj konsekwentnie dwóch spacji na poziom. W przypadku poważnego błędu PHP (biała strona) może być konieczne sprawdzenie logów serwera lub pliku syslog.
Formularze, konfiguracja i uprawnienia
Dodawanie formularza konfiguracyjnego modułu
Większość przydatnych modułów w Drupalu oferuje jakąś formę konfiguracji – choćby prostą stronę ustawień. Do tworzenia formularzy służy API Form API, które opiera się na strukturach tablicowych deklarujących elementy formularza. W Drupalu 8+ typowo tworzy się klasy formularzy dziedziczące po ConfigFormBase, gdy formularz służy do zapisu ustawień konfiguracyjnych.
W katalogu src utwórz folder Form, a w nim plik SettingsForm.php. Przykładowy formularz:
<?php
namespace Drupal\my_example_module\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
class SettingsForm extends ConfigFormBase {
protected function getEditableConfigNames() {
return [’my_example_module.settings’];
}
public function getFormId() {
return 'my_example_module_settings_form’;
}
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config(’my_example_module.settings’);
$form[’message’] = [
'#type’ => 'textfield’,
'#title’ => $this->t(’Komunikat powitalny’),
'#default_value’ => $config->get(’message’) ?: 'Witaj w moim module!’,
'#required’ => TRUE,
];
return parent::buildForm($form, $form_state);
}
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
$this->configFactory->getEditable(’my_example_module.settings’)
->set(’message’, $form_state->getValue(’message’))
->save();
}
}
Ten formularz zapisuje wartość w przestrzeni konfiguracji my_example_module.settings pod kluczem message. Dzięki temu możesz potem odczytywać te ustawienia w kontrolerze czy innych częściach modułu. To podstawowy sposób przechowywania konfiguracji w Drupalu, wspierany przez system eksportu i importu ustawień (Configuration Management).
Dodanie trasy do formularza i pozycji w menu
Aby móc wyświetlić formularz w panelu administracyjnym, dodaj kolejną trasę w pliku routing.yml, np.:
my_example_module.settings:
path: '/admin/config/my-example/settings’
defaults:
_form: '\Drupal\my_example_module\Form\SettingsForm’
_title: 'Ustawienia modułu My example’
requirements:
_permission: 'administer my example module’
Aby formularz był widoczny w menu konfiguracji, dodaj plik my_example_module.links.menu.yml:
my_example_module.settings_link:
title: 'Ustawienia My example module’
description: 'Zarządzanie ustawieniami modułu demonstracyjnego.’
parent: system.admin_config_system
route_name: my_example_module.settings
weight: 100
Teraz po przejściu do sekcji Konfiguracja → System (lub innej wskazanej przez parent) zobaczysz link prowadzący do formularza ustawień modułu. To standardowy sposób integrowania się z interfejsem administracyjnym Drupala.
Definiowanie uprawnień w pliku permissions.yml
Aby lepiej kontrolować dostęp do stron modułu, warto zdefiniować własne uprawnienia. Utwórz plik my_example_module.permissions.yml z treścią:
administer my example module:
title: 'Zarządzanie ustawieniami modułu My example’
description: 'Pozwala edytować konfigurację modułu demonstracyjnego.’
restrict access: TRUE
Po wyczyszczeniu cache w panelu administracyjnym (lub przez Drush) nowe uprawnienie pojawi się w zakładce Uprawnienia. Teraz możesz wiązać konkretne role użytkowników z możliwością modyfikacji ustawień modułu. Zastosowanie własnych uprawnień zamiast ogólnych (np. administer site configuration) zapewnia większe bezpieczeństwo i elastyczność w zarządzaniu dostępem.
Wykorzystanie konfiguracji w logice modułu
Skoro formularz zapisuje komunikat powitalny w konfiguracji, warto użyć tej wartości na stronie modułu. W kontrolerze możesz odczytać ją za pomocą:
$config = $this->config(’my_example_module.settings’);
$message = $config->get(’message’);
Następnie przekaż komunikat do tablicy renderującej, np.:
return [
'#type’ => 'markup’,
'#markup’ => Markup::create('<p>’ . $this->escapeHtml($message) . '</p>’),
];
Dzięki temu administrator może zmieniać treść komunikatu bez edycji kodu. Jest to przykład podstawowego, ale bardzo częstego wzorca w tworzeniu modułów: formularz konfiguracyjny, zapis ustawień w systemie konfiguracji, a następnie użycie tych ustawień w kontrolerze, bloku czy innym elemencie interfejsu.
Integracja z API Drupala i rozbudowa modułu
Hooki – reagowanie na zdarzenia systemowe
Jednym z fundamentów tworzenia modułów jest korzystanie z hooków, czyli funkcji wywoływanych przez Drupal w odpowiednich momentach. Pozwalają one reagować na zdarzenia, zmieniać zachowanie innych modułów czy modyfikować dane w locie. Przykładowo, aby dodać komunikat na każdej stronie, możesz użyć hook_page_attachments_alter w pliku my_example_module.module:
<?php
use Drupal\Core\Render\Markup;
function my_example_module_page_attachments_alter(array &$attachments) {
$attachments[’#attached’][’drupalSettings’][’myExample’][’message’] = 'To jest komunikat z modułu.’;
}
Hooki implementuje się jako funkcje w przestrzeni globalnej z prefiksem nazwy modułu. Drupal automatycznie wykrywa i wywołuje je w odpowiednich momentach cyklu życia żądania. Lista dostępnych hooków jest obszerna i obejmuje m.in. operacje na użytkownikach, węzłach, formularzach czy renderowaniu.
Usługi (services) i kontener zależności
Nowoczesne moduły w Drupalu korzystają szeroko z systemu services i kontenera zależności. Wiele funkcji jądra Drupala jest udostępnianych jako usługi, np. menedżer encji, logger, menedżer kolejki czy klient HTTP. Zamiast wywoływać je statycznie, wstrzykuje się je do klas (np. kontrolerów) za pomocą tzw. dependency injection.
Jeśli chcesz np. zalogować informację w kontrolerze, możesz wstrzyknąć usługę logger.factory lub skorzystać z metody z klasy ControllerBase. Przykład użycia:
$this->logger(’my_example_module’)->notice(’Strona modułu została wyświetlona.’);
Dzięki temu każde wyświetlenie strony modułu zostanie zarejestrowane w logach, co bywa pomocne przy analizie ruchu lub debugowaniu. Korzystanie z usług zamiast bezpośredniego tworzenia obiektów zapewnia większą elastyczność i lepsze testowanie kodu.
Tworzenie bloków jako pluginów
Oprócz stron można tworzyć także własne bloki, które administrator umieszcza w regionach motywu. W Drupalu bloki są realizowane jako pluginy. Aby utworzyć blok, dodaj klasę w katalogu src/Plugin/Block, np. MyExampleBlock.php:
<?php
namespace Drupal\my_example_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a 'My example’ block.
*
* @Block(
* id = „my_example_block”,
* admin_label = @Translation(„My example block”)
* )
*/
class MyExampleBlock extends BlockBase {
public function build() {
return [
'#markup’ => $this->t(’To jest blok z modułu demonstracyjnego.’),
];
}
}
Po wyczyszczeniu cache blok pojawi się w sekcji Struktura → Układ bloków, skąd można go umieścić w wybranym regionie. W bardziej rozbudowanej wersji możesz dodać konfigurację bloku (formularz ustawień), korzystając z metod blockForm i blockSubmit, podobnie jak w przypadku formularzy konfiguracyjnych modułów.
Przechowywanie danych i praca z encjami
Drupal oferuje rozbudowane API encji (entity API), które pozwala operować na węzłach, użytkownikach, taksonomiach i innych typach danych w spójny sposób. Własny moduł może tworzyć, odczytywać, aktualizować i usuwać encje bez pisania bezpośrednich zapytań SQL. Przykładowo, aby pobrać ostatnie pięć węzłów danego typu, możesz użyć menedżera encji node.storage:
$storage = \Drupal::entityTypeManager()->getStorage(’node’);
$nids = $storage->getQuery()
->condition(’status’, 1)
->condition(’type’, 'article’)
->range(0, 5)
->sort(’created’, 'DESC’)
->execute();
$nodes = $storage->loadMultiple($nids);
Następnie możesz przekazać te encje do szablonu Twig lub tablicy renderującej, by wyświetlić ich tytuły i fragmenty treści. To podejście jest dużo bezpieczniejsze i stabilniejsze niż operowanie bezpośrednio na bazie danych, ponieważ respektuje warstwy abstrakcji i integruje się z innymi modułami oraz systemem uprawnień.