Traits w PHP

Traits w PHP są obecne już od wersji 5.4, więc od dość długiego czasu (2012). Sam pierwszy raz czytałem o nich parę lat później, jednak nie wydały mi się atrakcyjne – są próbą wypełnienia braku dziedziczenia wielokrotnego, które nigdy w praktyce mi się nie przydawało jeszcze w czasach C++, a rodziło problemy, które komplikowały czasem bardziej niż jego brak. Zapomniałem więc o traits na wiele lat, nie widziałem też w tym czasie dla nich zastosowania (podobnie, jak wyparłem kiedyś dziedziczenie wielobazowe).

Aż do zeszłego roku. Trafiłem wtedy na zagadnienie, w którym przydatne byłoby wykorzystanie tego samego kodu w trzech zupełnie różnych klasach, z których każda miała już swoją dość złożoną strukturę i odpadało stworzenie klasy nadrzędnej, po której dziedziczyłyby wszystkie z nich.

Można było niby skorzystać z klasy helpera i pobierać jego instancję lub wstrzykiwać, jednak to też wprowadzało pewien nieład zwłaszcza, że jedna z tych klas modyfikowała działanie części kodu, którą potrzebowałem “uwspólnić”.

Nie było więc innego wyjścia, niż zainteresować się traitsami. I był to strzał w dziesiątkę. Traitsy to tak naprawdę kawałki kodu do wielokrotnego użytku. Wyglądają jak klasy, jednak nie wprowadzają zmian w strukturze typów. Nie narzucają też praktycznie niczego – używane w nich pola i metody nie muszą istnieć w nich samych, jeżeli wprowadzają konflikty, to można je łatwo rozwiązać. Nie można utworzyć ich samodzielnych instancji. Teoretycznie blisko jest im więc do klas abstrakcyjnych, z tą różnicą, że w PHP możemy dziedziczyć tylko jednej klasie abstrakcyjnej, natomiast traitstów możemy użyć tyle, ile potrzebujemy.

Korzystanie z nich jest więc podobne do skopiowania ich treści do bieżącej klasy. Dodatkowo, możemy aliasować poszczególne traitsy, a także metody z nich tak, aby nie zachodziły konflikty z już istniejącymi metodami, do których zaciągamy trait. Metody zaciągane z traits mogą być nadpisywane lokalnie, działa to wtedy tak, jak byśmy nadpisywali metodę z klasy nadrzędnej. Nadal można przy tym korzystać z nadpisywanej metody z trait.

Jeżeli chodzi o wady, to należy pamiętać o konfliktach w przypadku współistnienia pól.

Przyznam, że od czasu pierwszego użycia, pomijając początkowo neoficką fascynację, zacząłem widzieć coraz więcej obszarów, w których mógłbym stosować traitsy, i coraz częściej pojawiają się one w moim kodzie.

Widoczność pól i metod innych obiektów tego samego typu w PHP

Ostatnio, refaktoryzując kod, dowiedziałem się o pewnej ciekawej właściwości PHP. Otóż obiekty tych samych typów mają dostęp do prywatnych i chronionych metod oraz pól nawet, jeżeli stanowią oddzielne instancje tej samej klasy. Początkowo myślałem, że to jakiś błąd w kodzie, który analizowałem, jednak znalazłem potwierdzenie w manualu PHP:

Objects of the same type will have access to each others private and protected members even though they are not the same instances. This is because the implementation specific details are already known when inside those objects.

http://php.net/manual/en/language.oop5.visibility.php#language.oop5.visibility-other-objects

Co ciekawsze, w innych popularnych językach jest podobnie, więc nie jest to swoistość samego PHP, na co nigdy nie zwróciłem uwagi…

PSR-1 i PSR-2

Tym razem krótko, bo chciałem tylko zaznaczyć istnienie PSR-1 i PSR-2. Cieszę się, że te dwa zalecenia powstały i że modne jest stosowanie się do nich, przez co istnieje duże prawdopodobieństwo, że przeglądając źródła zarówno swojego projektu, jak i innych, dostępnych publicznie, a często używanych, trafimy na czytelne i zbieżne formatowanie. Sam musiałem wyplenić kilka złych nawyków, ale dzięki kod jest dużo czytelniejszy.

Wojny edycyjne uznajemy więc za zakończone. 😉

Ratchet – WebSockety w PHP w praktyce

WebSockety to jeden z najciekawszych od kilku lat tematów, z którymi przyszło mi zmierzyć się w pracy. Pierwszy raz słyszałem o nich gdzieś w 2013 roku i od tego czasu zdążyły zdobyć ogromną popularność w internecie. YouTube, Stack Overflow, Facebook – tam wszędzie WebSockety wykorzystywane są już od dawna do różnych rzeczy (przesyłanie i odświeżanie komentarzy oraz głosów, komunikacja w czasie rzeczywistym, itp.). To tyle słowem wstępu – jeżeli tu trafiłeś, prawdopodobnie wszystko to wiesz i szukasz informacji, jak wykorzystać WebSockety w praktyce.

Do zeszłego roku “WebSockety” i “node.js” stanowiły w zasadzie synonimy, jeżeli chodzi o ich implementację. O uruchomieniu ich w PHP czytałem same złe rzeczy i nawet najbardziej zagorzali programiści używający tego języka odradzali. Tematu jednak nie zgłębiałem i przyglądałem się nieco z boku. Jednak gdy w pracy pojawił się temat obsługi zdarzeń z centrali telefonicznej w naszym systemie, nie było wyjścia – trzeba zacząć się nimi szybko interesować.

Kolega z sąsiedniego zespołu już jakiś czas wcześniej podejmował ten temat w oparciu właśnie od node.js. Jego opinie nie były życzliwe, zwłaszcza, że nie miał wiele czasu na stworzenie tego rozwiązania. Podobnie z czasem było u mnie. Dodatkowo zniechęcał fakt, że w zasadzie nie mamy żadnej infrastruktury pod node.js, a ostatnią rzeczą, której bym chciał, to zarządzanie tym wszystkim od strony administracyjnej. Zacząłem więc szukać czegoś o WebSocketach w PHP.

Natychmiast trafiłem na Ratchet – bibliotekę do prostej implementacji WebSocketów w aplikacjach PHP. Brzmiało świetnie zwłaszcza, że mogłem korzystać z “dobrodziejstw” całego naszego systemu. W praktyce okazało się, że jest jeszcze lepiej i łatwiej, niż mi się wydawało.

Stwórzmy więc wspólnie skręcaną z drutów aplikację z pomocą Ratchet, która będzie niczym innym, jak prostym echotestem. Dodatkowo, oprócz odpowiadania na otrzymane wiadomości, serwer będzie sam wysyłał do wszystkich wiadomość, np. co 10 sekund. Dzięki temu otrzymasz bazę do większości zastosowań – nasza aplikacja będzie zarówno odpowiadała na komunikaty z inicjatywy klientów, jak i sama w pętli wysyłała coś od siebie.

Najpierw potrzebujemy samego Ratchet. Najłatwiej zlecić pobranie go composerowi. Jeżeli nie korzystałeś jeszcze z composera, spróbuj po prostu odwiedzić stronę pobierania i przejdź do sekcji “Manual download”, skąd możesz ściągnąć najnowszą wersję composer.phar. Umieść ten plik w katalogu głównym projektu, po czym możesz przejść do pobierania Ratcheta:

php composer.phar require cboden/ratchet

Kolejnym krokiem będzie stworzenie klasy obsługującej wszystkie potrzebne zdarzenia (połączenie, rozłączenie, wystąpienie błędu) oraz zawierającej metodę, którą serwer będzie cyklicznie wywoływać. Oto jej treść:

<?php
namespace WebSocket;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Handler implements MessageComponentInterface
{
    protected $clients;

    public function __construct()
    {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn);

        echo 'Nowe połączenie: ' . $conn->resourceId . PHP_EOL;
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
        // iteracja po dostępnych połączeniach
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // bieżące połączenie to nadawca wiadomości
                
                // odsyłamy wiadomość
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);

        echo 'Zamknięto połączenie: ' . $conn->resourceId . PHP_EOL;
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo 'Wystąpił błąd: ' . $e->getMessage() . PHP_EOL;

        $conn->close();
    }
    
    public function onLoop($options = array())
    {
        foreach($this->clients as $client) {
            $client->send('Cykliczna wiadomość');
    
            echo 'Wysłano komunikat cykliczny' . PHP_EOL;
        }
    }
}

Klasę umieściłem w pliku handler.php.

Metody onOpen, onClose i onError są raczej oczywiste. W onMessage występuje obsługa zdarzenia pojawiającego się w momencie otrzymania wiadomości od klienta. Iterujemy tam po prostu po tablicy dostępnych połączeń i sprawdzamy, czy trafiliśmy na połączenie zgodne z identyfikatorem zasobu nadawcy. Jeżeli tak, to korzystamy z tego zasobu do odesłania odpowiedzi tej samej treści, którą otrzymaliśmy.

Metodę onLoop wykorzystamy natomiast do wywoływania cyklicznego. Wysyła ona do wszystkich połączeń komunikat o tej samej treści.

Mając taką klasę, możemy stworzyć plik, w którym uruchomimy sam serwer. W moim wypadku jest to server.php o następującej treści:

<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use WebSocket\Handler;

require 'vendor/autoload.php';
require 'handler.php';

// utworzenie obiektu klasy obsługującej
$handler = new Handler();

// stworzenie instancji serwera websocketów
$server = IoServer::factory(
    new HttpServer(
        new WsServer($handler)
    ),
    8080    // port
);

// ustawienie pętli
$server->loop->addPeriodicTimer(10, function() use ($server, $handler) {
    // uruchomienie metody obrotu pętli
    $handler->onLoop(array(
        'server' => $server,
    ));
});

echo 'Serwer uruchomiony' . PHP_EOL;

// uruchomienie serwera
$server->run();

Jak widać, tworzymy najpierw instancję serwera nasłuchującego na porcie 8080, podając mu klasę z obsłużonymi zdarzeniami, a następnie dodajemy pętlę, która będzie wywoływana co 10 sekund.

Teraz wystarczy uruchomić serwer poprzez wywołanie:

php server.php

W konsoli powinniśmy dostawać informacje o przebiegu działania serwera.

Jak się połączyć z naszym serwerem? Najprościej uruchamiając konsolę w przeglądarce (w Firefoksie możemy wywołać ją kombinacją CTRL-SHIFT-K) i wklejając:

var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
    console.log("Połączono pomyślnie!");
};

conn.onmessage = function(e) {
    console.log(e.data);
};

Po chwili powinniśmy otrzymywać z serwera co 10 sekund wiadomość.

Aby wysłać wiadomość do serwera, wystarczy, że w tej samej konsoli wkleimy:

conn.send('Test');

To wszystko! Mamy już działający serwer.

Oczywiście, docelowa aplikacja musi zawierać obsługę uwierzytelniania, jednak celem tego wpisu było pokazanie prostoty stworzenia serwera WebSocketów.

Wpis był mocno inspirowany oficjalnym tutorialem Ratcheta, który rozbudowałem o cykliczne wywoływanie zdarzeń przez serwer.

Eclipse – utrata możliwości debugowania w trakcie używania Xdebug

UWAGA! Ten wpis ma już 5 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Po poznaniu rozwiązania problem wydaje się być głupi, ale powstrzymał mnie skutecznie przez bardzo długi czas przed debugowaniem aplikacji w PHP z użyciem Xdebug.

Do sedna: jeżeli w trakcie debugowania nagle tracisz możliwość sterowania debuggerem (dezaktywują się przyciski od nawigowania po kodzie, itp.), ale sama aplikacja i sesja debuggera nadal działa (jesteś w stanie ją pomyślnie zakończyć), to prawdopodobnie Eclipse stracił focus na debugowanym przez Ciebie wątku. Stan taki pokazuje poniższy zrzut ekranu:

eclipse-xdebug-lost-focus

Co w takiej sytuacji? Wystarczy kliknąć na “Remote launch” w karcie “Debug” (u mnie jest to lewa górna część ekranu):

eclipse-xdebug-recovered-focus

Jak widać, przyciski do sterowania debuggerem są znów aktywne i można kontynuować tę sesję Xdebug.