PHP 8.2: Co nowego?
Nowa wersja PHP jest tuż za rogiem. Jakie są nowe implementacje, o których powinieneś wiedzieć? Sprawdź ten artykuł, aby się dowiedzieć!

Przeczytaj pierwszą część naszej serii PHP poświęconej komunikacji mikroserwisów we frameworku Symfony i najpopularniejszemu sposobowi - komunikacji AMQP z wykorzystaniem RabbitMQ.
Nowoczesna architektura aplikacji wymusiła na deweloperach zmianę sposobu myślenia o komunikacji pomiędzy poszczególnymi komponentami systemów informatycznych. Kiedyś sprawa była prostsza - większość systemów tworzona była jako monolityczne struktury połączone ze sobą siecią powiązań logiki biznesowej. Utrzymanie takich zależności w PHP projekt było ogromnym wyzwaniem dla Programiści PHPa także rosnąca popularność rozwiązań SaaS i ogromny wzrost popularności chmura Usługi spowodowały, że dziś coraz częściej słyszymy o mikrousługach i modułowości aplikacji.
W jaki sposób, tworząc niezależne mikrousługi, możemy sprawić, by wymieniały one między sobą informacje?
Ten artykuł jest pierwszym z serii postów na temat Komunikacja mikrousług w Symfony i obejmuje najpopularniejszy sposób - komunikację AMQP przy użyciu RabbitMQ.
Aby utworzyć dwie niezależne aplikacje i osiągnąć komunikację między nimi przy użyciu tylko magistrali komunikatów.
Mamy dwie wyimaginowane, niezależne aplikacje:
* app1
: który wysyła powiadomienia e-mail i SMS do pracowników
* app2
który pozwala zarządzać pracą pracowników i przydzielać im zadania.
Chcemy stworzyć nowoczesny i prosty system, dzięki któremu przydzielanie pracy pracownikowi w app2
wyśle powiadomienie do klienta przy użyciu app1
. Wbrew pozorom jest to bardzo proste!
Na potrzeby tego artykułu użyjemy najnowszej wersji Symfony (wersja 6.1 w momencie pisania) i najnowszej wersji PHP (8.1). W kilku bardzo prostych krokach stworzymy działające lokalne środowisko Docker z dwoma mikroserwisami. Wszystko czego potrzebujesz to:
* działający komputer,
* zainstalowany Docker + Środowisko Docker Compose
* i lokalnie skonfigurowany Symfony CLI i trochę wolnego czasu.
Wykorzystamy możliwości Dockera jako narzędzia do wirtualizacji i konteneryzacji aplikacji. Zacznijmy od utworzenia drzewa katalogów, szkieletu dla dwóch Aplikacje Symfonyi opisać infrastrukturę naszych środowisk przy użyciu docker-compose.yml
plik.
cd ~
mkdir microservices-in-symfony
cd microservices-in-symfony
symfony new app1
symfony new app2
touch docker-compose.yml
Stworzyliśmy dwa katalogi dla dwóch oddzielnych aplikacji Symfony i utworzyliśmy pusty katalog docker-compose.yml
aby uruchomić nasze środowisko.
Dodajmy następujące sekcje do docker-compose.yml
file:
version: '3.8'
usługi:
app1:
containername: app1
build: app1/.
restart: on-failure
envfile: app1/.env
environment:
APPNAME: app1
tty: true
stdinopen: true
app2:
containername: app2
build: app2/.
restart: on-failure
envfile: app2/.env
environment:
APPNAME: app2
tty: true
stdinopen: true
rabbitmq:
containername: rabbitmq
image: rabbitmq:management
ports:
- 15672:15672
- 5672:5672
environment:
- RABBITMQDEFAULTUSER=user
- RABBITMQDEFAULT_PASS=hasło
Źródło kod dostępne bezpośrednio: thecodest-co/microservices-in-symfony/blob/main/docker-compose.yml
Ale zaraz, co tu się stało? Dla osób niezaznajomionych z Dockerem powyższy plik konfiguracyjny może wydawać się enigmatyczny, jednak jego cel jest bardzo prosty. Korzystając z Docker Compose budujemy trzy "usługi":
Do prawidłowego działania nadal potrzebujemy Plik Docker
które są źródłem do tworzenia obrazów. Więc stwórzmy je:
touch app1/Dockerfile
touch app2/Dockerfile
Oba pliki mają dokładnie taką samą strukturę i wyglądają następująco:
Z php:8.1
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
COPY . /app/
WORKDIR /app/
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions && sync &&
install-php-extensions amqp
RUN apt-get update
&& apt-get install -y libzip-dev wget --no-install-recommends
&& apt-get clean
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN docker-php-ext-install zip;
CMD bash -c "cd /app && composer install && php -a"
Kod źródłowy dostępny bezpośrednio: /thecodest-co/microservices-in-symfony/blob/main/app1/Dockerfile
Powyższy plik jest używany przez Docker Compose do zbudowania kontenera z obrazu PHP 8.1 z zainstalowanym Composerem i rozszerzeniem AMQP. Dodatkowo uruchamia intepreter PHP w trybie append, aby kontener działał w tle.
Drzewo katalogów i plików powinno teraz wyglądać następująco:
.
├── app1
│ └── Dockerfile
| (...) # Struktura aplikacji Symfony
├── app2
│ └── Dockerfile
| (...) # Struktura aplikacji Symfony
└── docker-compose.yml
Zacznijmy od app1
i pierwszej aplikacji.
W naszym przykładzie jest to aplikacja, która nasłuchuje i pobiera wiadomości z kolejki wysyłanej przez app2
zgodnie z opisem w wymaganiach:
przypisanie zadania do pracownika w
app2
wyśle powiadomienie do klienta
Zacznijmy od dodania wymaganych bibliotek. AMQP jest natywnie obsługiwany dla symfony/messenger
rozszerzenie. Dodatkowo zainstalujemy monolog/monolog
do śledzenia dzienników systemowych w celu łatwiejszej analizy zachowania aplikacji.
cd app1/
symfony composer req amqp ampq-messenger monolog
Po instalacji dodano dodatkowy plik w sekcji config/packages/messenger.yaml
. Jest to plik konfiguracyjny dla komponentu Symfony Messenger i nie potrzebujemy go pełna konfiguracja.
Zastąp go poniższym plikiem YAML:
ramy:
messenger:
# Usuń ten komentarz (i nieudany transport poniżej), aby wysyłać nieudane wiadomości do tego transportu w celu późniejszej obsługi.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
external_messages:
dsn: "%env(MESSENGER_TRANSPORT_DSN)%
options:
auto_setup: false
exchange:
name: messages
typ: bezpośredni
default_publish_routing_key: from_external
queues:
messages:
binding_keys: [from_external]
Kod źródłowy dostępny bezpośrednio: thecodest-co/microservices-in-symfony/blob/main/app1/config/packages/messenger.yaml
Symfony Messenger służy do synchronicznej i asynchronicznej komunikacji w aplikacjach Symfony. Obsługuje wiele różnych transportyczyli źródła prawdy warstwy transportowej. W naszym przykładzie używamy rozszerzenia AMQP, które obsługuje system kolejek zdarzeń RabbitMQ.
Powyższa konfiguracja definiuje nowy transport o nazwie external_messages
który odwołuje się do MESSENGER_TRANSPORT_DSN
i definiuje bezpośrednie nasłuchiwanie na wiadomości
w Message Bus. W tym momencie należy również edytować app1/.env
i dodać odpowiedni adres transportowy.
"`env
(...)
MESSENGERTRANSPORTDSN=amqp://user:password@rabbitmq:5672//messages
(...)
"
Po przygotowaniu szkieletu aplikacji i skonfigurowaniu bibliotek, nadszedł czas na implementację logiki biznesowej. Wiemy, że nasza aplikacja musi reagować na przypisanie zadania do pracownika. Wiemy również, że przypisanie zadania wapp2system zmienia status zadania. Stwórzmy więc model, który naśladuje zmianę statusu i zapiszmy go w ścieżceapp1/Message/StatusUpdate.php`:
{
public function __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Kod źródłowy dostępny bezpośrednio: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Message/StatusUpdate.php
Nadal potrzebujemy klasy, która zaimplementuje logikę biznesową, gdy nasza mikrousługa otrzyma powyższe zdarzenie z kolejki. Stwórzmy więc klasę Obsługa komunikatów w app1/Handler/StatusUpdateHandler.php
ścieżka:
use PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
class StatusUpdateHandler
{
public function __construct(
protected LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP1: {STATUS_UPDATE} - '.$statusDescription);
// reszta logiki biznesowej, tj. wysyłanie wiadomości e-mail do użytkownika
// $this->emailService->email()
}
}
Kod źródłowy dostępny bezpośrednio: /thecodest-co/microservices-in-symfony/blob/main/app1/src/Handler/StatusUpdateHandler.php
PHP znacznie ułatwiają sprawę i oznaczają, że w tym konkretnym przypadku nie musimy martwić się o autowiring czy deklarację usługi. Nasza mikrousługa do obsługi zdarzeń domenowych jest gotowa, czas zabrać się za drugą aplikację.
Przyjrzymy się app2
i drugi katalog Aplikacja Symfony. Naszą ideą jest wysyłanie wiadomości do kolejki, gdy pracownik otrzyma zadanie w systemie. Wykonajmy więc szybką konfigurację AMQP i uruchommy naszą drugą mikrousługę, aby rozpocząć publikację StatusUpdate
zdarzeń do magistrali komunikatów.
Instalacja bibliotek przebiega dokładnie tak samo, jak w przypadku pierwszej aplikacji.
cd ..
cd app2/
symfony composer req amqp ampq-messenger monolog
Upewnijmy się, że app2/.env
zawiera prawidłowy wpis DSN dla RabbitMQ:
(...)
MESSENGER_TRANSPORT_DSN=amqp://user:password@rabbitmq:5672//messages
(...)
Pozostaje tylko skonfigurować Symfony Messenger w sekcji app2/config/packages/messenger.yaml
file:
ramy:
messenger:
# Usuń ten komentarz (i nieudany transport poniżej), aby wysyłać nieudane wiadomości do tego transportu w celu późniejszej obsługi.
# failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# Przekieruj wiadomości do transportów
'AppMessageStatusUpdate': async
Jak widać, tym razem definicja transportu wskazuje bezpośrednio na asynchroniczny
i definiuje routing w postaci wysyłania naszych StatusUpdate
do skonfigurowanej sieci DSN. Jest to jedyny obszar konfiguracji, pozostaje tylko stworzyć logikę i warstwę implementacji kolejki AMQP. W tym celu utworzymy bliźniaczą StatusUpdateHandler
i StatusUpdate
zajęcia w app2
.
use PsrLogLoggerInterface;
use SymfonyComponentMessengerAttributeAsMessageHandler;
[AsMessageHandler]
class StatusUpdateHandler
{
public function __construct(
private readonly LoggerInterface $logger,
) {}
public function __invoke(StatusUpdate $statusUpdate): void
{
$statusDescription = $statusUpdate->getStatus();
$this->logger->warning('APP2: {STATUS_UPDATE} - '.$statusDescription);
## logika biznesowa, tj. wysyłanie wewnętrznych powiadomień lub kolejkowanie innych systemów
}
}
{
public function __construct(protected string $status){}
public function getStatus(): string
{
return $this->status;
}
}
Kod źródłowy dostępny bezpośrednio: /thecodest-co/microservices-in-symfony/blob/main/app2/src/Message/StatusUpdate.php
Na koniec wszystko, co należy zrobić, to stworzyć sposób wysyłania wiadomości do magistrali komunikatów. Stworzymy prostą aplikację Polecenie Symfony za to:
use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentMessengerMessageBusInterface;
[AsCommand(
name: "app:send"
)]
class SendStatusCommand extends Command
{
public function construct(private readonly MessageBusInterface $messageBus, string $name = null)
{
parent::construct($name);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$status = "Pracownik X przypisany do Y";
$this->messageBus->dispatch(
message: new StatusUpdate($status)
);
return Command::SUCCESS;
}
}
Dzięki Wstrzykiwanie zależności możemy użyć instancji MessageBusInterface
w naszym poleceniu i wysłać StatusUpdate
wiadomość za pośrednictwem dispatch()
do naszej kolejki. Dodatkowo używamy tutaj również atrybutów PHP.
To wszystko - pozostaje tylko uruchomić nasze środowisko Docker Compose i sprawdzić, jak zachowują się nasze aplikacje.
Dzięki Docker Compose kontenery z naszymi dwiema aplikacjami zostaną zbudowane i uruchomione jako oddzielne instancje, a jedyną warstwą pośredniczącą będzie rabbitmq
i nasza implementacja magistrali komunikatów.
Z katalogu głównego projektu uruchommy następujące polecenia:
cd ../ # upewnij się, że jesteś w katalogu głównym
docker-compose up --build -d
To polecenie może zająć trochę czasu, ponieważ buduje dwa oddzielne kontenery z PHP 8.1 + AMQP i pobiera obraz RabbitMQ. Bądź cierpliwy. Po zbudowaniu obrazów możesz uruchomić naszą komendę z poziomu app2
i wysłać kilka wiadomości do kolejki.
docker exec -it app2 php bin/console app:send
Można to robić dowolną liczbę razy. Tak długo, jak nie ma konsument wiadomości nie będą przetwarzane. Jak tylko uruchomisz aplikację app1
i konsumować wszystkie wiadomości, które będą wyświetlane na ekranie.
docker exec -it app1 php bin/console messenger:consume -vv external_messages
Kompletny kod źródłowy wraz z plikiem README można znaleźć w naszym publicznym repozytorium The Codest Github
Symfony ze swoimi bibliotekami i narzędziami pozwala na szybkie i wydajne podejście do tworzenia nowoczesnych aplikacji. aplikacje internetowe. Za pomocą kilku komend i kilku linijek kodu jesteśmy w stanie stworzyć nowoczesny system komunikacji pomiędzy aplikacjami. Symfony, podobnie jak PHPjest idealny dla tworzenie aplikacji internetowych a dzięki swojemu ekosystemowi i łatwości wdrożenia osiąga jedne z najlepszych wskaźników czasu wprowadzenia na rynek.
Jednak szybko nie zawsze znaczy dobrze - w powyższym przykładzie przedstawiliśmy najprostszy i najszybszy sposób komunikacji. Co bardziej dociekliwi z pewnością zauważą, że brakuje odłączenia zdarzeń domenowych poza warstwę aplikacji - w obecnej wersji są one dublowane, nie ma też pełnego wsparcia dla Koperta
między innymi nie ma Znaczki
. Tych i innych zapraszam do lektury części II, w której poruszymy temat ujednolicania struktury domenowej aplikacji Symfony w środowisku mikrousług oraz omówimy drugą popularną metodę komunikacji mikrousług - tym razem synchroniczną, opartą o REST API.