Samouczek Magento 2: Jak zbudować kompletny moduł

Opublikowany: 2022-03-11

Magento to obecnie największa na świecie platforma eCommerce typu open source. Ze względu na bogatą w funkcje i rozszerzalną bazę kodu, handlowcy prowadzący duże i małe operacje na całym świecie używają go do wielu różnych projektów.

Magento 1 istnieje od ośmiu lat, a jego następca, Magento 2, został wydany pod koniec 2015 roku, poprawiając słabe punkty wcześniejszej wersji, takie jak:

  • Poprawiona wydajność
  • Oficjalny zestaw testów automatycznych
  • Lepszy interfejs użytkownika zaplecza
  • Nowa, bardziej nowoczesna baza kodu front-end
  • Bardziej modułowy sposób tworzenia modułów, z plikami zawartymi w kodzie Magento zamiast rozproszonymi po całym miejscu
  • Zmniejszona liczba konfliktów między modułami próbującymi dostosować tę samą funkcjonalność

Stylizowane logo Magento 2

Po nieco ponad roku widać poprawę, mimo że nie wszystkie wymienione problemy zostały całkowicie rozwiązane. Teraz można całkowicie bezpiecznie powiedzieć, że Magento 2 jest znacznie bardziej solidnym oprogramowaniem niż jego poprzednik. Niektóre z ulepszeń obecnych w Magento 2 to:

  • Testy jednostkowe i integracyjne, w tym oficjalny i udokumentowany sposób tworzenia ich dla niestandardowych modułów
  • Moduły, które są naprawdę zmodularyzowane, a wszystkie ich pliki znajdują się w jednym katalogu
  • Bogatszy system szablonów, pozwalający programiście motywów na tworzenie n-poziomowej hierarchii szablonów
  • Szereg przydatnych wzorców projektowych przyjętych w całym kodzie, poprawiających jakość kodu i zmniejszających prawdopodobieństwo błędów tworzonych przez moduły — obejmują one między innymi automatyczne wstrzykiwanie zależności, umowy serwisowe, repozytoria i fabryki.
  • Natywna integracja z Varnish jako system pełnego buforowania stron, a także Redis do obsługi sesji i pamięci podręcznej
  • Obsługa PHP 7

Krzywa uczenia się dla Magento 2, przy wszystkich tych zmianach, stała się jeszcze bardziej stroma. W tym poradniku zamierzam pokazać Ci jak rozwijać swój pierwszy moduł Magento 2 i wskazać Ci właściwy kierunek do kontynuowania nauki. Weźmy się za to!

Wymagania wstępne samouczka Magento 2

Ważne jest, aby dobrze zrozumieć następujące technologie/koncepcje, aby móc śledzić resztę tego artykułu:

  • Programowanie obiektowe (OOP)
  • PHP
  • Przestrzenie nazw
  • MySQL
  • Podstawowe użycie bash

Z powyższego OOP jest prawdopodobnie najważniejszym. Magento zostało początkowo stworzone przez zespół doświadczonych programistów Java, a ich spuściznę z pewnością można zobaczyć w całym kodzie. Jeśli nie masz pewności co do swoich umiejętności OOP, dobrym pomysłem może być przejrzenie ich przed rozpoczęciem pracy z platformą.

Przegląd architektury Magento 2

Architektura Magento została zaprojektowana z zamiarem uczynienia kodu źródłowego tak zmodularyzowanym i rozszerzalnym, jak to tylko możliwe. Ostatecznym celem tego podejścia jest umożliwienie łatwego dostosowania i dostosowania do potrzeb każdego projektu.

Dostosowywanie zazwyczaj oznacza zmianę zachowania kodu platformy. W większości systemów oznacza to zmianę „podstawowego” kodu. W Magento, jeśli stosujesz najlepsze praktyki, jest to coś, czego możesz uniknąć przez większość czasu, dzięki czemu sklep może być na bieżąco z najnowszymi poprawkami bezpieczeństwa i wydaniami funkcji w niezawodny sposób.

Magento 2 to system Model View ViewModel (MVVM). Będąc blisko spokrewnionym z siostrzanym kontrolerem widoku modelu (MVC), architektura MVVM zapewnia bardziej niezawodne rozdzielenie między warstwami modelu i widoku. Poniżej znajduje się wyjaśnienie każdej z warstw systemu MVVM:

  • Model przechowuje logikę biznesową aplikacji i zależy od skojarzonej klasy — ResourceModel — dostępu do bazy danych. Modele opierają się na umowach serwisowych , aby udostępnić swoją funkcjonalność innym warstwom aplikacji.
  • Widok to struktura i układ tego, co użytkownik widzi na ekranie — rzeczywisty kod HTML. Osiąga się to w plikach PHTML dystrybuowanych z modułami. Pliki PHTML są powiązane z każdym modelem ViewModel w plikach Layout XML , które w dialekcie MVVM są określane jako bindery. Pliki układu mogą również przypisywać pliki JavaScript do użycia na końcowej stronie.
  • ViewModel współdziała z warstwą Model, udostępniając tylko niezbędne informacje warstwie Widok. W Magento 2 jest to obsługiwane przez klasy Block modułu. Zauważ, że była to zwykle część roli kontrolera systemu MVC. Na MVVM kontroler jest odpowiedzialny tylko za obsługę przepływu użytkownika, co oznacza, że ​​odbiera żądania i albo mówi systemowi, aby wyrenderował widok, albo przekierował użytkownika na inną trasę.

Moduł Magento 2 składa się z niektórych, jeśli nie wszystkich, elementów architektury opisanej powyżej. Ogólna architektura jest opisana poniżej (źródło):

Schemat pełnej architektury Magento 2

Moduł Magento 2 może z kolei definiować zależności zewnętrzne za pomocą Composera, menedżera zależności PHP. Na powyższym diagramie widać, że podstawowe moduły Magento 2 zależą od Zend Framework, Symfony oraz innych bibliotek firm trzecich.

Poniżej znajduje się struktura Magento/Cms, głównego modułu Magento 2 odpowiedzialnego za obsługę tworzenia stron i bloków statycznych.

Układ katalogów modułu Magento/Cms

Każdy folder zawiera jedną część architektury, w następujący sposób:

  • Api: Umowy serwisowe, definiowanie interfejsów usług i interfejsów danych
  • Blok: ViewModele naszej architektury MVVM
  • Kontroler: Kontrolery, odpowiedzialne za obsługę przepływu użytkownika podczas interakcji z systemem
  • itp: Pliki konfiguracyjne XML — moduł definiuje siebie i swoje części (trasy, modele, bloki, obserwatorzy i zadania cron) w tym folderze. Pliki etc mogą być również używane przez moduły inne niż podstawowe, aby zastąpić funkcjonalność modułów podstawowych.
  • Helper: klasy pomocnicze, które przechowują kod używany w więcej niż jednej warstwie aplikacji. Np. w module Cms klasy pomocnicze odpowiedzialne są za przygotowanie HTML do prezentacji w przeglądarce.
  • i18n: Zawiera pliki CSV z internacjonalizacją, używane do tłumaczenia
  • Model: dla modeli i modeli zasobów
  • Obserwator: Posiada obserwatorów lub modele, które „obserwują” zdarzenia systemowe. Zwykle, gdy takie zdarzenie jest uruchamiane, obserwator tworzy instancję Modelu, aby obsłużyć niezbędną logikę biznesową dla takiego zdarzenia.
  • Konfiguracja: Klasy migracji odpowiedzialne za tworzenie schematu i danych
  • Test: testy jednostkowe
  • Ui: elementy interfejsu użytkownika, takie jak siatki i formularze używane w aplikacji administratora
  • widok: Pliki układu (XML) i pliki szablonów (PHTML) dla aplikacji front-end i administratora

Warto również zauważyć, że w praktyce wszystkie wewnętrzne mechanizmy Magento 2 znajdują się w module. Na powyższym obrazku widać na przykład Magento_Checkout , odpowiedzialne za proces realizacji zamówienia oraz Magento_Catalog , odpowiedzialne za obsługę produktów i kategorii. Zasadniczo mówi nam to, że nauka pracy z modułami jest najważniejszą częścią zostania programistą Magento 2.

W porządku, po tym stosunkowo krótkim wprowadzeniu do architektury systemu i struktury modułów, zróbmy coś bardziej konkretnego, dobrze? Następnie przejdziemy przez tradycyjny samouczek dotyczący bloga internetowego, aby zapewnić Ci komfort korzystania z Magento 2 i być na dobrej drodze do zostania programistą Magento 2. Wcześniej musimy skonfigurować środowisko programistyczne. Weźmy się za to!

Konfigurowanie środowiska programowania modułów Magento 2

W momencie pisania tego tekstu mogliśmy korzystać z oficjalnego Magento 2 DevBox, który jest kontenerem Magento 2 Docker. Docker na macOS to coś, co nadal uważam za bezużyteczne, przynajmniej w systemie, który w dużym stopniu zależy od szybkiego dysku I/O, takim jak Magento 2. Tak więc zrobimy to w tradycyjny sposób: zainstaluj wszystkie pakiety natywnie na naszym własnym komputerze.

Konfiguracja serwera

Instalacja wszystkiego z pewnością jest nieco bardziej żmudna, ale efektem końcowym będzie błyskawiczne środowisko programistyczne Magento. Uwierz mi, zaoszczędzisz godziny pracy, nie polegając na Dockerze podczas tworzenia Magento 2.

W tym samouczku założono środowisko w systemie macOS z zainstalowanym programem Brew. Jeśli tak nie jest w Twoim przypadku, podstawy pozostaną takie same, zmieniając tylko sposób instalowania pakietów. Zacznijmy od zainstalowania wszystkich pakietów:

 brew install mysql nginxb php70 php70-imagick php70-intl php70-mcrypt

Następnie uruchom usługi:

 brew services start mysql brew services start php70 sudo brew services start nginx

Ok, teraz wskażemy domenę na nasz adres zwrotny. Otwórz plik hosts w dowolnym edytorze, ale upewnij się, że masz uprawnienia administratora. Zrobienie tego z Vimem byłoby:

 sudo vim /etc/hosts

Następnie dodaj następujący wiersz:

 127.0.0.1 magento2.dev

Teraz stworzymy vhost w Nginx:

 vim /usr/local/etc/nginx/sites-available/magento2dev.conf

Dodaj następującą treść:

 server { listen 80; server_name magento2.dev; set $MAGE_ROOT /Users/yourusername/www/magento2dev; set $MAGE_MODE developer; # Default magento Nginx config starts below root $MAGE_ROOT/pub; index index.php; autoindex off; charset off; add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block'; location / { try_files $uri $uri/ /index.php?$args; } location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; } location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/ { try_files $uri $uri/ /get.php?$args; location ~ ^/media/theme_customization/.*\.xml { deny all; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; try_files $uri $uri/ /get.php?$args; } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; try_files $uri $uri/ /get.php?$args; } add_header X-Frame-Options "SAMEORIGIN"; } location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ /media/theme_customization/.*\.xml$ { deny all; } location /errors/ { try_files $uri =404; } location ~ ^/errors/.*\.(xml|phtml)$ { deny all; } location ~ cron\.php { deny all; } location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=768M \n max_execution_time=60"; fastcgi_read_timeout 60s; fastcgi_connect_timeout 60s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } # Default magento Nginx config finishes below client_max_body_size 20M; }

Jeśli nie miałeś wcześniej do czynienia z Nginx, ten plik może Cię przestraszyć, więc wyjaśnijmy to tutaj, ponieważ rzuci on również trochę światła na niektóre wewnętrzne działania Magento. Pierwsze wiersze po prostu informują Nginx, że używamy domyślnego portu HTTP, a naszą domeną jest magento2.dev :

 listen 80; server_name magento2.dev;

Następnie ustawiamy kilka zmiennych środowiskowych. Pierwsza — $MAGE_ROOT — przechowuje ścieżkę do naszej bazy kodu. Zauważ, że będziesz musiał zmienić ścieżkę główną, aby dopasować nazwę użytkownika/ścieżkę do folderu, gdziekolwiek planujesz umieścić źródło na:

 set $MAGE_ROOT /Users/yourusername/www/magento2dev;

Druga zmienna $MAGE_MODE — ustawia tryb działania naszego sklepu. Podczas tworzenia modułu będziemy korzystać z trybu programisty. Dzięki temu kodujemy szybciej, ponieważ nie będziemy musieli kompilować ani wdrażać plików statycznych podczas programowania. Pozostałe tryby to produkcja i domyślny. Prawdziwe zastosowanie tego ostatniego nie jest jeszcze jasne.

 set $MAGE_MODE developer;

Po ustawieniu tych zmiennych definiujemy ścieżkę główną vhosta. Zauważ, że do zmiennej $MAGE_ROOT folder /pub , dzięki czemu tylko część naszego sklepu jest dostępna w sieci.

 root $MAGE_ROOT/pub;

Następnie definiujemy nasz plik indeksu — plik nginx zostanie załadowany, gdy żądany plik nie istnieje — jako index.php. Ten skrypt, $MAGE_ROOT/pub/index.php , jest głównym punktem wejścia dla klientów odwiedzających zarówno koszyk, jak i aplikacje administracyjne. Niezależnie od żądanego adresu URL, index.php zostanie załadowany i rozpocznie się proces wysyłania routera.

 index index.php;

Następnie wyłączamy niektóre funkcje Nginx. Najpierw wyłączamy autoindex , który wyświetla listę plików, gdy żądasz folderu, ale nie określasz pliku i nie ma indeksu. Po drugie, wyłączamy charset , co pozwoliłoby Nginx na automatyczne dodawanie nagłówków Charset do odpowiedzi.

 autoindex off; charset off;

Następnie definiujemy kilka nagłówków bezpieczeństwa:

 add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-XSS-Protection' '1; mode=block';

Ta lokalizacja, / , jest wskazywana na nasz folder główny $MAGE_ROOT/pub i zasadniczo przekierowuje każde otrzymane żądanie do naszego kontrolera frontowego index.php, wraz z argumentami żądania:

 location / { try_files $uri $uri/ /index.php?$args; }

Następna część może być nieco zagmatwana, ale jest dość prosta. Kilka linijek temu zdefiniowaliśmy nasz root jako $MAGE_ROOT/pub . Jest to zalecana i bezpieczniejsza konfiguracja, ponieważ większość kodu nie jest widoczna z sieci. Ale to nie jedyny sposób na skonfigurowanie serwera WWW. W rzeczywistości większość współdzielonych serwerów internetowych ma jedną domyślną konfigurację, która polega na tym, aby serwer WWW wskazywał folder WWW. Dla tych użytkowników zespół Magento przygotował ten plik na wypadek, gdy root jest zdefiniowany jako $MAGE_ROOT z następującym fragmentem:

 location /pub { location ~ ^/pub/media/(downloadable|customer|import|theme_customization/.*\.xml) { deny all; } alias $MAGE_ROOT/pub; add_header X-Frame-Options "SAMEORIGIN"; }

Pamiętaj, że zawsze, gdy to możliwe, najlepiej, jeśli serwer WWW wskazuje folder $MAGE_ROOT/pub . W ten sposób Twój sklep będzie bezpieczniejszy.

Następnie mamy statyczną lokalizację $MAGE_ROOT/pub/static . Ten folder jest początkowo pusty i automatycznie wypełniany plikami statycznymi modułów i motywów, takimi jak pliki obrazów, CSS, JS itp. Tutaj zasadniczo definiujemy niektóre wartości pamięci podręcznej dla plików statycznych, a jeśli żądany plik nie istnieje, przekieruj go do $MAGE_ROOT/pub/static.php . Skrypt ten między innymi przeanalizuje żądanie i skopiuje lub dowiąże symbolicznie określony plik z odpowiedniego modułu lub motywu, w zależności od zdefiniowanego trybu wykonawczego. W ten sposób pliki statyczne Twojego modułu będą znajdować się w folderze naszych modułów, ale będą obsługiwane bezpośrednio z folderu publicznego vhost:

 location /static/ { if ($MAGE_MODE = "production") { expires max; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { add_header Cache-Control "public"; add_header X-Frame-Options "SAMEORIGIN"; expires +1y; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } location ~* \.(zip|gz|gzip|bz2|csv|xml)$ { add_header Cache-Control "no-store"; add_header X-Frame-Options "SAMEORIGIN"; expires off; if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } } if (!-f $request_filename) { rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last; } add_header X-Frame-Options "SAMEORIGIN"; }

Następnie odmawiamy dostępu do sieci do niektórych ograniczonych folderów i plików:

 location /media/customer/ { deny all; } location /media/downloadable/ { deny all; } location /media/import/ { deny all; } location ~ /media/theme_customization/.*\.xml$ { deny all; } location /errors/ { try_files $uri =404; } location ~ ^/errors/.*\.(xml|phtml)$ { deny all; } location ~ cron\.php { deny all; }

A ostatni bit to miejsce, w którym ładujemy php-fpm i mówimy mu, aby wykonał index.php za każdym razem, gdy użytkownik go uderzy:

 location ~ (index|get|static|report|404|503)\.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_param PHP_FLAG "session.auto_start=off \n suhosin.session.cryptua=off"; fastcgi_param PHP_VALUE "memory_limit=768M \n max_execution_time=60"; fastcgi_read_timeout 60s; fastcgi_connect_timeout 60s; fastcgi_param MAGE_MODE $MAGE_MODE; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }

Pomijając to, zapisz plik, a następnie włącz go, wpisując następujące polecenia:

 ln -s /usr/local/etc/nginx/sites-available/magento2dev.conf \ /usr/local/etc/nginx/sites-enabled/magento2dev.conf sudo brew services restart nginx

Jak zainstalować Magento 2

Dobra, w tym momencie twoja maszyna spełnia wymagania Magento 2, brakuje tylko samej bestii. Udaj się na stronę Magento i utwórz konto, jeśli nadal go nie masz. Następnie przejdź do strony pobierania i pobierz najnowszą wersję (2.1.5, w momencie pisania):

Strona pobierania Magento 2

Wybierz format .tar.bz2 i pobierz go. Następnie przejdź do wyodrębnienia i ustaw odpowiednie uprawnienia do folderów i plików, aby Magento 2 mogło działać:

 mkdir ~/www/magento2dev cd ~/www/magento2dev tar -xjf ~/Downloads/Magento-CE-2.1.5-2017-02-20-05-39-14.tar.bz2 find var vendor pub/static pub/media app/etc -type f -exec chmod u+w {} \; find var vendor pub/static pub/media app/etc -type d -exec chmod u+w {} \; chmod u+x bin/magento

Teraz, aby zainstalować tabele bazy danych i utworzyć potrzebne pliki konfiguracyjne, uruchomimy to polecenie z terminala:

 ./bin/magento setup:install --base-url=http://magento2.dev/ \ --db-host=127.0.0.1 --db-name=magento2 --db-user=root \ --db-password=123 --admin-firstname=Magento --admin-lastname=User \ [email protected] --admin-user=admin \ --admin-password=admin123 --language=en_US --currency=USD \ --timezone=America/Chicago --use-rewrites=1 --backend-frontname=admin

Pamiętaj, aby zmienić nazwę bazy danych ( db-name ), użytkownika ( db-user ) i hasło ( db-password ) na takie, których użyłeś podczas instalacji MySQL i to wszystko! To polecenie zainstaluje wszystkie moduły Magento 2, tworząc wymagane tabele i pliki konfiguracyjne. Po zakończeniu otwórz przeglądarkę i przejdź do http://magento2.dev/. Powinieneś zobaczyć czystą instalację Magento 2 z domyślnym motywem Luma:

Strona główna w domyślnym motywie Lumy

Jeśli przejdziesz do http://magento2.dev/admin, powinieneś zobaczyć stronę logowania do aplikacji Admin:

Strona logowania do aplikacji administratora

Następnie użyj poniższych danych logowania, aby się zalogować:

Użytkownik: admin Hasło: admin123

Jesteśmy w końcu gotowi do pisania naszego kodu!

Tworzenie naszego pierwszego modułu Magento 2

Aby ukończyć nasz moduł, będziemy musieli stworzyć następujące pliki, a ja przeprowadzę Cię przez cały proces. Będziemy potrzebować:

  • Kilka standardowych plików rejestracyjnych, aby Magento wiedziało o naszym module Blog
  • Jeden plik interfejsu, aby zdefiniować naszą umowę dotyczącą danych dla Poczty
  • Post Model reprezentujący Post w całym naszym kodzie, implementujący interfejs danych Post
  • Post Resource Model, aby połączyć Post Model z bazą danych
  • Zbiór postów, aby pobrać kilka postów naraz z bazy danych za pomocą modelu zasobów
  • Dwie klasy migracji, aby skonfigurować nasz schemat tabeli i zawartość
  • Dwie akcje: jedna, aby wyświetlić wszystkie posty, a druga, aby wyświetlić każdy post z osobna
  • Po dwa pliki bloków, widoków i układów: po jednym dla akcji listy i po jednym dla widoku

Najpierw przyjrzyjmy się strukturze folderów z podstawowym kodem źródłowym, abyśmy mogli określić, gdzie umieścić nasz kod. Sposób, w jaki zainstalowaliśmy, zawiera cały kod rdzenia Magento 2, wraz ze wszystkimi jego zależnościami, znajdujący się w folderze vendor kompozytora.

Układ katalogów podstawowego kodu Magento 2

Rejestracja naszego modułu

Będziemy przechowywać nasz kod w osobnym folderze app/code . Nazwa każdego modułu ma postać Namespace_ModuleName , a jego lokalizacja w systemie plików musi odzwierciedlać tę nazwę, app/code/Namespace/ModuleName dla tego przykładu. Zgodnie z tym wzorcem nazwiemy nasz moduł Toptal_Blog i umieścimy nasze pliki w app/code/Toptal/Blog . Śmiało i utwórz tę strukturę folderów.

Układ katalogów naszego modułu Toptal_Blog

Teraz musimy stworzyć kilka standardowych plików, aby nasz moduł został zarejestrowany w Magento. Najpierw utwórz app/code/Toptal/Blog/composer.json :

 {}

Ten plik będzie ładowany przez Composer za każdym razem, gdy go uruchomisz. Mimo że w rzeczywistości nie używamy Composera z naszym modułem, musimy go utworzyć, aby Composer był zadowolony.

Teraz zarejestrujemy nasz moduł w Magento. Śmiało i utwórz app/code/Toptal/Blog/registration.php :

 <?php \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, 'Toptal_Blog', __DIR__ );

W tym miejscu wywołujemy metodę register klasy ComponentRegistrar , wysyłając dwa parametry: ciąg 'module' , który jest typem rejestrowanego komponentu, oraz nazwę naszego modułu 'Toptal_Blog' . Dzięki tym informacjom autoloader Magento będzie świadomy naszej przestrzeni nazw i będzie wiedział, gdzie szukać naszych klas i plików XML.

Warto zauważyć, że mamy typ komponentu ( MODULE ) wysyłany jako parametr do funkcji \Magento\Framework\Component\ComponentRegistrar::register . Możemy rejestrować nie tylko moduły, ale także inne rodzaje komponentów. Na przykład motywy, biblioteki zewnętrzne i pakiety językowe są również rejestrowane przy użyciu tej samej metody.

Kontynuując, utwórzmy nasz ostatni plik rejestracyjny, app/code/Toptal/Blog/etc/module.xml :

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd"> <module name="Toptal_Blog" setup_version="0.1.0"> <sequence> <module name="Magento_Directory" /> <module name="Magento_Config" /> </sequence> </module> </config>

Ten plik zawiera bardzo ważne informacje o naszym module. Oni są:

  • Nazwa modułu jest ponownie obecna, eksponując nazwę naszego modułu w konfiguracji Magento.
  • Wersja instalacji Magento, która będzie używana przez Magento do decydowania, kiedy uruchomić skrypty migracji bazy danych.
  • Zależności naszego modułu — Pisząc prosty moduł, polegamy tylko na dwóch podstawowych modułach Magento: Magento_Directory i Magento_Config .

Teraz mamy moduł, który powinien być rozpoznawalny przez Magento 2. Sprawdźmy to za pomocą Magento 2 CLI.

Najpierw musimy wyłączyć pamięć podręczną Magento. Mechanizmy cache Magento zasługują na poświęcony im artykuł. Na razie, ponieważ rozwijamy moduł i chcemy, aby nasze zmiany były natychmiast rozpoznawane przez Magento bez konieczności ciągłego czyszczenia pamięci podręcznej, po prostu go wyłączymy. Z wiersza poleceń uruchom:

 ./bin/magento cache:disable

Następnie zobaczmy, czy Magento jest już świadomy naszych modyfikacji, patrząc na stan modułów. Po prostu uruchom następujące polecenie:

 ./bin/magento module:status

Wynik z ostatniego powinien być podobny do:

Wyjście polecenia statusu, pokazujące wyłączony moduł Toptal_Blog

Nasz moduł jest tam, ale jak pokazuje dane wyjściowe, nadal jest wyłączony. Aby go włączyć, uruchom:

 ./bin/magento module:enable Toptal_Blog

To powinno wystarczyć. Dla pewności możesz ponownie wywołać module:status i poszukać nazwy naszego modułu na włączonej liście:

Dane wyjściowe polecenia statusu, pokazujące włączony moduł Toptal_Blog

Obsługa przechowywania danych

Teraz, gdy włączyliśmy nasz moduł, musimy utworzyć tabelę bazy danych, w której będą przechowywane nasze posty na blogu. Oto schemat tabeli, którą chcemy utworzyć:

Pole Rodzaj Zero Klucz Domyślna
post_id int(10) bez znaku NIE PRI ZERO
tytuł tekst NIE ZERO
zawartość tekst NIE ZERO
utworzony_w znak czasu NIE CURRENT_TIMESTAMP

Osiągamy to, tworząc klasę InstallSchema , która jest odpowiedzialna za zarządzanie instalacją naszej migracji schematu. Plik znajduje się w app/code/Toptal/Blog/Setup/InstallSchema.php i ma następującą zawartość:

 <?php namespace Toptal\Blog\Setup; use \Magento\Framework\Setup\InstallSchemaInterface; use \Magento\Framework\Setup\ModuleContextInterface; use \Magento\Framework\Setup\SchemaSetupInterface; use \Magento\Framework\DB\Ddl\Table; /** * Class InstallSchema * * @package Toptal\Blog\Setup */ class InstallSchema implements InstallSchemaInterface { /** * Install Blog Posts table * * @param SchemaSetupInterface $setup * @param ModuleContextInterface $context */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); $tableName = $setup->getTable('toptal_blog_post'); if ($setup->getConnection()->isTableExists($tableName) != true) { $table = $setup->getConnection() ->newTable($tableName) ->addColumn( 'post_id', Table::TYPE_INTEGER, null, [ 'identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true ], 'ID' ) ->addColumn( 'title', Table::TYPE_TEXT, null, ['nullable' => false], 'Title' ) ->addColumn( 'content', Table::TYPE_TEXT, null, ['nullable' => false], 'Content' ) ->addColumn( 'created_at', Table::TYPE_TIMESTAMP, null, ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], 'Created At' ) ->setComment('Toptal Blog - Posts'); $setup->getConnection()->createTable($table); } $setup->endSetup(); } }

Jeśli przeanalizujesz metodę install , zauważysz, że po prostu tworzy naszą tabelę i dodaje jej kolumny jedna po drugiej.

Aby określić, kiedy należy uruchomić migrację schematu, Magento przechowuje tabelę ze wszystkimi bieżącymi wersjami konfiguracji dla każdego modułu, a za każdym razem, gdy wersja modułu się zmienia, inicjowane są jego klasy migracji. Ta tabela to setup_module , a jeśli spojrzysz na jej zawartość, zobaczysz, że jak dotąd nie ma odniesienia do naszego modułu. Więc zmieńmy to. Z terminala uruchom następujące polecenie:

 ./bin/magento setup:upgrade

Spowoduje to wyświetlenie listy wszystkich modułów i skryptów migracji, które zostały wykonane, w tym naszych:

Dane wyjściowe polecenia aktualizacji, pokazujące, jak przeprowadzana jest nasza migracja

Teraz, korzystając z preferowanego klienta MySQL, możesz sprawdzić, czy tabela naprawdę została utworzona:

Demonstracja naszej tabeli w kliencie MySQL

A w tabeli setup_module znajduje się teraz odniesienie do naszego modułu, jego schematu i wersji danych:

Zawartość tabeli setup_module

Ok, a co z aktualizacjami schematów? Dodajmy kilka postów do tej tabeli poprzez aktualizację, aby pokazać, jak to zrobić. Najpierw setup_version w naszym etc/module.xml :

Wyróżnij zmienioną wartość w naszym pliku module.xml

Teraz tworzymy nasz plik app/code/Toptal/Blog/Setup/UpgradeData.php , który odpowiada za migracje danych (nie schematów):

 <?php namespace Toptal\Blog\Setup; use \Magento\Framework\Setup\UpgradeDataInterface; use \Magento\Framework\Setup\ModuleContextInterface; use \Magento\Framework\Setup\ModuleDataSetupInterface; /** * Class UpgradeData * * @package Toptal\Blog\Setup */ class UpgradeData implements UpgradeDataInterface { /** * Creates sample blog posts * * @param ModuleDataSetupInterface $setup * @param ModuleContextInterface $context * @return void */ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) { $tableName = $setup->getTable('toptal_blog_post'); $data = [ [ 'title' => 'Post 1 Title', 'content' => 'Content of the first post.', ], [ 'title' => 'Post 2 Title', 'content' => 'Content of the second post.', ], ]; $setup ->getConnection() ->insertMultiple($tableName, $data); } $setup->endSetup(); } }

Widać, że jest bardzo podobny do naszej klasy Install. Jedyną różnicą jest to, że implementuje interfejs UpgradeDataInterface zamiast InstallSchemaInterface , a główna metoda nosi nazwę upgrade . Za pomocą tej metody sprawdzasz zainstalowaną wersję bieżącego modułu i, gdy jest ona mniejsza niż twoja, uruchamiasz zmiany, które musisz zrobić. W naszym przykładzie sprawdzamy, czy aktualna wersja jest mniejsza niż 0.1.1 w poniższym wierszu za pomocą funkcji version_compare :

 if ($context->getVersion() && version_compare($context->getVersion(), '0.1.1') < 0 ) {

$context->getVersion() zwróci 0.1.0 przy pierwszym wywołaniu polecenia setup:upgrade CLI. Następnie przykładowe dane są ładowane do bazy danych, a nasza wersja jest podbijana do 0.1.1. Aby to uruchomić, setup:upgrade :

 ./bin/magento setup:upgrade

A następnie sprawdź wyniki w tabeli postów:

Zawartość naszej tabeli

A w tabeli setup_module :

Zaktualizowana zawartość tabeli setup_module

Zauważ, że chociaż dodaliśmy dane do naszej tabeli za pomocą procesu migracji, możliwa byłaby również zmiana schematu. Proces jest taki sam; użyjesz tylko UpgradeSchemaInterface zamiast UpgradeDataInterface .

Definiowanie modelu dla postów

Idąc dalej, jeśli pamiętasz nasz przegląd architektury, naszym następnym elementem konstrukcyjnym będzie post na blogu ResourceModel. Model zasobów jest bardzo prosty i po prostu określa tabelę, z którą model będzie się „połączyć”, wraz z tym, jaki jest jego klucz podstawowy. Stworzymy nasz ResourceModel w app/code/Toptal/Blog/Model/ResourceModel/Post.php z następującą zawartością:

 <?php namespace Toptal\Blog\Model\ResourceModel; use \Magento\Framework\Model\ResourceModel\Db\AbstractDb; class Post extends AbstractDb { /** * Post Abstract Resource Constructor * @return void */ protected function _construct() { $this->_init('toptal_blog_post', 'post_id'); } }

Wszystkie operacje ResourceModel, chyba że potrzebujesz czegoś innego niż zwykłe operacje CRUD, są obsługiwane przez klasę nadrzędną AbstractDb .

Będziemy również potrzebować innego ResourceModel, kolekcji. Kolekcja będzie odpowiedzialna za przeszukiwanie bazy danych pod kątem wielu postów przy użyciu naszego ResourceModel i dostarczanie z powrotem serii utworzonych i wypełnionych informacjami modeli. Tworzymy plik app/code/Toptal/Blog/Model/ResourceModel/Post/Collection.php o następującej zawartości:

 <?php namespace Toptal\Blog\Model\ResourceModel\Post; use \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; class Collection extends AbstractCollection { /** * Remittance File Collection Constructor * @return void */ protected function _construct() { $this->_init('Toptal\Blog\Model\Post', 'Toptal\Blog\Model\ResourceModel\Post'); } }

Zauważ, że w konstruktorze po prostu wspominamy Model, który będzie reprezentował encję post w całym naszym kodzie, oraz ResourceModel, który będzie pobierał informacje z bazy danych.

Brakującym elementem tej warstwy jest sam Post Model. Model powinien zawierać wszystkie atrybuty, które zdefiniowaliśmy w naszym schemacie, wraz z dowolną logiką biznesową, której możesz potrzebować. Podążając za wzorcem Magento 2, musimy stworzyć interfejs danych, z którego rozszerzy się nasz model. Interfejs umieszczamy w app/code/Toptal/Blog/Api/Data/PostInterface.php i powinien on zawierać nazwy pól tabeli wraz z metodami dostępu do nich:

 <?php namespace Toptal\Blog\Api\Data; interface PostInterface { /**#@+ * Constants for keys of data array. Identical to the name of the getter in snake case */ const POST_; const TITLE = 'title'; const CONTENT = 'content'; const CREATED_AT = 'created_at'; /**#@-*/ /** * Get Title * * @return string|null */ public function getTitle(); /** * Get Content * * @return string|null */ public function getContent(); /** * Get Created At * * @return string|null */ public function getCreatedAt(); /** * Get ID * * @return int|null */ public function getId(); /** * Set Title * * @param string $title * @return $this */ public function setTitle($title); /** * Set Content * * @param string $content * @return $this */ public function setContent($content); /** * Set Crated At * * @param int $createdAt * @return $this */ public function setCreatedAt($createdAt); /** * Set ID * * @param int $id * @return $this */ public function setId($id); }

Przejdźmy teraz do implementacji modelu w app/code/Toptal/Blog/Model/Post.php . Stworzymy metody zdefiniowane na interfejsie. Określimy również tag pamięci podręcznej poprzez stałą CACHE_TAG , a w konstruktorze określimy ResourceModel, który będzie odpowiedzialny za dostęp do bazy danych dla naszego modelu.

 <?php namespace Toptal\Blog\Model; use \Magento\Framework\Model\AbstractModel; use \Magento\Framework\DataObject\IdentityInterface; use \Toptal\Blog\Api\Data\PostInterface; /** * Class File * @package Toptal\Blog\Model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Post extends AbstractModel implements PostInterface, IdentityInterface { /** * Cache tag */ const CACHE_TAG = 'toptal_blog_post'; /** * Post Initialization * @return void */ protected function _construct() { $this->_init('Toptal\Blog\Model\ResourceModel\Post'); } /** * Get Title * * @return string|null */ public function getTitle() { return $this->getData(self::TITLE); } /** * Get Content * * @return string|null */ public function getContent() { return $this->getData(self::CONTENT); } /** * Get Created At * * @return string|null */ public function getCreatedAt() { return $this->getData(self::CREATED_AT); } /** * Get ID * * @return int|null */ public function getId() { return $this->getData(self::POST_ID); } /** * Return identities * @return string[] */ public function getIdentities() { return [self::CACHE_TAG . '_' . $this->getId()]; } /** * Set Title * * @param string $title * @return $this */ public function setTitle($title) { return $this->setData(self::TITLE, $title); } /** * Set Content * * @param string $content * @return $this */ public function setContent($content) { return $this->setData(self::CONTENT, $content); } /** * Set Created At * * @param string $createdAt * @return $this */ public function setCreatedAt($createdAt) { return $this->setData(self::CREATED_AT, $createdAt); } /** * Set ID * * @param int $id * @return $this */ public function setId($id) { return $this->setData(self::POST_ID, $id); } }

Creating Views

Now we are moving one layer up, and will start the implementation of our ViewModel and Controller. To define a route in the front-end (shopping cart) application, we need to create the file app/code/Toptal/Blog/etc/frontend/routes.xml with the following contents:

 <?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> <router> <route frontName="blog"> <module name="Toptal_Blog"/> </route> </router> </config>

List of Posts at the Index Page

Here, we are basically telling Magento that our module, Toptal_Blog , will be responsible for responding to routes under http://magento2.dev/blog (notice the frontName attribute of the route). Next up is the action, at app/code/Toptal/Blog/Controller/Index/Index.php :

 <?php namespace Toptal\Blog\Controller\Index; use \Magento\Framework\App\Action\Action; use \Magento\Framework\View\Result\PageFactory; use \Magento\Framework\View\Result\Page; use \Magento\Framework\App\Action\Context; use \Magento\Framework\Exception\LocalizedException; class Index extends Action { /** * @var PageFactory */ protected $resultPageFactory; /** * @param Context $context * @param PageFactory $resultPageFactory * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, PageFactory $resultPageFactory ) { parent::__construct( $context ); $this->resultPageFactory = $resultPageFactory; } /** * Prints the blog from informed order id * @return Page * @throws LocalizedException */ public function execute() { $resultPage = $this->resultPageFactory->create(); return $resultPage; } }

Our action is defining two methods. Let us take a closer look at them:

  • The constructor method simply sends the $context parameter to its parent method, and sets the $resultPageFactory parameter to an attribute for later use. At this point it is useful to know the Dependency Injection design pattern, as that is what is happening here. In Magento 2's case we have automatic dependency injection. This means that whenever a class instantiation occurs, Magento will automatically try to instantiate all of the class constructor parameters (dependencies) and inject it for you as constructor parameters. It identifies which classes to instantiate for each parameter by inspecting the type hints, in this case Context and PageFactory .

  • The execute method is responsible for the action execution itself. In our case, we are simply telling Magento to render its layout by returning a Magento\Framework\View\Result\Page object. This will trigger the layout rendering process, which we will create in a bit.

Now you should see a blank page at the url http://magento2.dev/blog/index/index. We still need to define the layout structure for that route, and its corresponding Block (our ViewModel) and the template file which will present the data to our user.

The layout structure for the front-end application is defined under view/frontend/layout , and the file name must reflect our route. As our route is blog/index/index , the layout file for that route will be app/code/Toptal/Blog/view/frontend/layout/blog_index_index.xml :

 <?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Toptal\Blog\Block\Posts" name="posts.list" template="Toptal_Blog::post/list.phtml" /> </referenceContainer> </body> </page>

Here, we must define three very important structures in the Magento layout structure: Blocks, Containers, and Templates.

  • Blocks are the ViewModel part of our MVVM architecture, which was explained in earlier sections. They are the building blocks of our template structure.

  • Containers contain and output Blocks. They hold blocks together in nice hierarchical structures, and help in making things make sense when the layout for a page is being processed.

  • Templates are PHMTL (mixed HTML and PHP) files used by a special type of block in Magento. You can make calls to methods of a $block variable from within a template. The variable is always defined in the template context. You will be invoking your Block's methods by doing so, and thus allowing you to pull information from the ViewModel layer to the actual presentation.

With that extra information at hand, we can analyze the XML layout structure above. This layout structure is basically telling Magento that, when a request is made to the blog/index/index route, a Block of the type Toptal\Blog\Block\Posts is to be added to the content container, and the template which will be used to render it is Toptal_blog::post/list.phtml .

This leads us to the creation of our two remaining files. Our Block, located at app/code/Toptal/Blog/Block/Posts.php :

<?php namespace Toptal\Blog\Block; use \Magento\Framework\View\Element\Template; use \Magento\Framework\View\Element\Template\Context; use \Toptal\Blog\Model\ResourceModel\Post\Collection as PostCollection; use \Toptal\Blog\Model\ResourceModel\Post\CollectionFactory as PostCollectionFactory; use \Toptal\Blog\Model\Post; class Posts extends Template { /** * CollectionFactory * @var null|CollectionFactory */ protected $_postCollectionFactory = null; /** * Constructor * * @param Context $context * @param PostCollectionFactory $postCollectionFactory * @param array $data */ public function __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { $this->_postCollectionFactory = $postCollectionFactory; parent::__construct($context, $data); } /** * @return Post[] */ public function getPosts() { /** @var PostCollection $postCollection */ $postCollection = $this->_postCollectionFactory->create(); $postCollection->addFieldToSelect('*')->load(); return $postCollection->getItems(); } /** * For a given post, returns its url * @param Post $post * @return string */ public function getPostUrl( Post $post ) { return '/blog/post/view/id/' . $post->getId(); } }

Ta klasa jest dość prosta, a jej celem jest wyłącznie załadowanie postów, które mają być pokazane, i dostarczenie metody getPostUrl do szablonu. Jest jednak kilka rzeczy do zauważenia.

Jeśli pamiętasz, nie zdefiniowaliśmy Toptal\Blog\Model\ResourceModel\Post\CollectionFactory . Zdefiniowaliśmy tylko Toptal\Blog\Model\ResourceModel\Post\Collection . Więc jak to w ogóle działa? Dla każdej klasy, którą zdefiniujesz w swoim module, Magento 2 automatycznie utworzy dla Ciebie Fabrykę. Fabryki mają dwie metody: create , która zwróci nową instancję dla każdego wywołania i get , która zawsze zwróci tę samą instancję przy każdym wywołaniu — używana do implementacji wzorca Singleton.

Trzeci parametr naszego bloku, $data , to opcjonalna tablica. Ponieważ jest opcjonalny i nie ma podpowiedzi typu, nie zostanie wtryśnięty przez automatyczny system wtrysku. Należy zauważyć, że opcjonalne parametry konstruktora muszą być zawsze umieszczane na końcu parametrów. Na przykład konstruktor naszej klasy macierzystej Magento\Framework\View\Element\Template ma następujące parametry:

 public function __construct( Template\Context $context, array $data = [] ) { ...

Ponieważ chcieliśmy dodać naszą CollectionFactory do parametrów konstruktora po rozszerzeniu klasy Template, musieliśmy to zrobić przed parametrem opcjonalnym, w przeciwnym razie wstrzyknięcie nie zadziałałoby:

 public function __construct( Context $context, PostCollectionFactory $postCollectionFactory, array $data = [] ) { ...

W metodzie getPosts , do której później uzyska dostęp nasz szablon, po prostu wywołujemy metodę create z PostCollectionFactory , która zwróci nam świeżą PostCollection i pozwoli nam pobrać nasze posty z bazy danych i wysłać je do naszej odpowiedzi.

Aby zakończyć układ tej trasy, oto nasz szablon PHTML, app/code/Toptal/Blog/view/frontend/templates/post/list.phtml :

 <?php /** @var Toptal\Blog\Block\Posts $block */ ?> <h1>Toptal Posts</h1> <?php foreach($block->getPosts() as $post): ?> <?php /** @var Toptal\Blog\Model\Post */ ?> <h2><a href="<?php echo $block->getPostUrl($post);?>"><?php echo $post->getTitle(); ?></a></h2> <p><?php echo $post->getContent(); ?></p> <?php endforeach; ?>

Zauważ, że tutaj widzimy warstwę View uzyskującą dostęp do naszego ModelView ( $block->getPosts() ), który z kolei używa ResourceModel (kolekcji) do pobierania naszych modeli ( Toptal\Blog\Model\Post ) z bazy danych. W każdym szablonie, gdy chcesz uzyskać dostęp do metod jego bloku, będzie zdefiniowana zmienna $block i czeka na twoje wywołania.

Teraz powinieneś być w stanie zobaczyć listę postów, po prostu ponownie wchodząc na naszą trasę.

Nasza strona indeksowa, pokazująca listę postów

Przeglądanie poszczególnych postów

Teraz, jeśli klikniesz tytuł posta, otrzymasz 404, więc naprawmy to. Z całą naszą strukturą na miejscu, staje się to dość proste. Będziemy musieli tylko stworzyć:

  • Nowa akcja, odpowiedzialna za obsługę zgłoszeń do trasy blog/post/view
  • Blok do renderowania postu
  • Szablon PHTML, odpowiedzialny za sam widok
  • Plik układu dla trasy blogu/postu/przeglądania, łączący te ostatnie elementy razem.

Nasza nowa akcja jest dość prosta. Po prostu otrzyma id parametru z żądania i zarejestruje go w głównym rejestrze Magento, centralnym repozytorium informacji dostępnych w jednym cyklu żądania. W ten sposób udostępnimy później identyfikator blokowi. Plik powinien znajdować się w app/code/Toptal/Blog/Controller/Post/View.php i oto jego zawartość:

 <?php namespace Toptal\Blog\Controller\Post; use \Magento\Framework\App\Action\Action; use \Magento\Framework\View\Result\PageFactory; use \Magento\Framework\View\Result\Page; use \Magento\Framework\App\Action\Context; use \Magento\Framework\Exception\LocalizedException; use \Magento\Framework\Registry; class View extends Action { const REGISTRY_KEY_POST_; /** * Core registry * @var Registry */ protected $_coreRegistry; /** * @var PageFactory */ protected $_resultPageFactory; /** * @param Context $context * @param Registry $coreRegistry * @param PageFactory $resultPageFactory * * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( Context $context, Registry $coreRegistry, PageFactory $resultPageFactory ) { parent::__construct( $context ); $this->_coreRegistry = $coreRegistry; $this->_resultPageFactory = $resultPageFactory; } /** * Saves the blog id to the register and renders the page * @return Page * @throws LocalizedException */ public function execute() { $this->_coreRegistry->register(self::REGISTRY_KEY_POST_ID, (int) $this->_request->getParam('id')); $resultPage = $this->_resultPageFactory->create(); return $resultPage; } }

Zauważ, że dodaliśmy parametr $coreRegistry do naszego __construct i zapisaliśmy go jako atrybut do późniejszego wykorzystania. W metodzie execute pobieramy parametr id z żądania i rejestrujemy go. Używamy również stałej klasy self::REGISTRY_KEY_POST_ID jako klucza do rejestru i użyjemy tej samej stałej w naszym bloku, aby odwołać się do identyfikatora w rejestrze.

Stwórzmy blok w app/code/Toptal/Blog/Block/View.php o następującej treści:

 <?php namespace Toptal\Blog\Block; use \Magento\Framework\Exception\LocalizedException; use \Magento\Framework\View\Element\Template; use \Magento\Framework\View\Element\Template\Context; use \Magento\Framework\Registry; use \Toptal\Blog\Model\Post; use \Toptal\Blog\Model\PostFactory; use \Toptal\Blog\Controller\Post\View as ViewAction; class View extends Template { /** * Core registry * @var Registry */ protected $_coreRegistry; /** * Post * @var null|Post */ protected $_post = null; /** * PostFactory * @var null|PostFactory */ protected $_postFactory = null; /** * Constructor * @param Context $context * @param Registry $coreRegistry * @param PostFactory $postCollectionFactory * @param array $data */ public function __construct( Context $context, Registry $coreRegistry, PostFactory $postFactory, array $data = [] ) { $this->_postFactory = $postFactory; $this->_coreRegistry = $coreRegistry; parent::__construct($context, $data); } /** * Lazy loads the requested post * @return Post * @throws LocalizedException */ public function getPost() { if ($this->_post === null) { /** @var Post $post */ $post = $this->_postFactory->create(); $post->load($this->_getPostId()); if (!$post->getId()) { throw new LocalizedException(__('Post not found')); } $this->_post = $post; } return $this->_post; } /** * Retrieves the post id from the registry * @return int */ protected function _getPostId() { return (int) $this->_coreRegistry->registry( ViewAction::REGISTRY_KEY_POST_ID ); } }

W bloku widoku definiujemy chronioną metodę _getPostId , która po prostu pobierze identyfikator postu z rejestru podstawowego. Publiczna metoda getPost z kolei leniwie załaduje post i wyrzuci wyjątek, jeśli post nie istnieje. Wrzucenie tutaj wyjątku spowoduje, że Magento wyświetli domyślny ekran błędu, co może nie jest najlepszym rozwiązaniem w takim przypadku, ale dla uproszczenia tak pozostanie.

Przejdź do naszego szablonu PHTML. Dodaj app/code/Toptal/Blog/view/frontend/templates/post/view.phtml z następującą zawartością:

 <?php /** @var Toptal\Blog\Block\View $block */ ?> <h1><?php echo $block->getPost()->getTitle(); ?></h1> <p><?php echo $block->getPost()->getContent(); ?></p>

Ładne i proste, po prostu dostęp do metody getPost bloku widoku, którą stworzyliśmy wcześniej.

Aby to wszystko połączyć, tworzymy plik układu dla naszej nowej trasy w app/code/Toptal/Blog/view/frontend/layout/blog_post_view.xml z następującą zawartością:

 <?xml version="1.0"?> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="content"> <block class="Toptal\Blog\Block\View" name="post.view" template="Toptal_Blog::post/view.phtml" /> </referenceContainer> </body> </page>

Robi to samo, co robiliśmy wcześniej. Po prostu dodaje Toptal\Blog\Block\View do kontenera content , z Toptal_Blog::post/view.phtml jako powiązanym szablonem.

Aby zobaczyć go w akcji, po prostu skieruj przeglądarkę na http://magento2.dev/blog/post/view/id/1, aby pomyślnie załadować post. Powinieneś zobaczyć ekran taki jak ten poniżej:

Strona do wyświetlania poszczególnych postów

Jak widać, po utworzeniu naszej początkowej struktury bardzo łatwo jest dodać funkcje do platformy, a większość naszego początkowego kodu jest ponownie wykorzystywana w tym procesie.

Jeśli chcesz szybko przetestować moduł, oto całkowity wynik naszej pracy.

Gdzie iść stąd?

Jeśli śledziłeś mnie do tej pory, gratulacje! Jestem przekonany, że jesteś bardzo blisko zostania programistą Magento 2. Opracowaliśmy całkiem zaawansowany niestandardowy moduł Magento 2 i mimo że jest prosty w swoich funkcjach, omówiliśmy wiele zagadnień.

Niektóre rzeczy zostały pominięte w tym artykule dla uproszczenia. By wymienić tylko kilka:

  • Administrator edytuj formularze i siatki, aby zarządzać zawartością naszego bloga
  • Kategorie blogów, tagi i komentarze
  • Repozytoria i kilka umów serwisowych, które mogliśmy założyć
  • Pakowanie modułów w górę jako rozszerzenia Magento 2

W każdym razie, oto kilka przydatnych linków, dzięki którym możesz jeszcze bardziej pogłębić swoją wiedzę:

  • Blog Alan Storm na Magento 2 — Alan Storm ma prawdopodobnie najbardziej dydaktyczną zawartość, jeśli chodzi o naukę Magento.
  • Blog Alana Kenta
  • Dokumentacja Magento: Dokumentacja deweloperska Magento 2

Zapewniłem Ci obszerne wprowadzenie do wszystkich istotnych aspektów tworzenia modułu w Magento 2 oraz kilka dodatkowych zasobów, jeśli ich potrzebujesz. Teraz od Ciebie zależy, czy zaczniesz kodować, albo przejdź do komentarzy, jeśli chcesz wziąć udział.