Na ogół jako programiści PHP nie zastanawiamy się tak mocno nad typem zmiennych. Tym bardziej wiele osób używających frameworków korzysta z gotowych kodów, które mapują im pobrane dane do odpowiedniego typu z bazy na typ w PHP.
Za sobą mam kilka projektów w PHP np. system do zarządzania biletami i lożami w klubie muzycznym, bot kontrolujący serwer TeamSpeak 3, system monitorujący kilka procesów czy panel integrujący się z HP ALM do kontroli zgłoszeń testowych. Tworząc te czy inne projekty nie myślałem dokładnie nad typem zwracanych zmiennych przez bazę.
Jeżeli już potrzebowałem konkretnej zmiennej (o odpowiednim typie) to sobie ją rzutowałem do odpowiedniego typu.
Ostatnio tworząc projekt dla firmy, w której pracuje bardzo dużym problemem stały się typy danych. Pobierane one były z bazy MariaDB (fork MySQL). Nie potrzebowałem dużego framework’a, więc użyłem Codeigniter 3. Serwer w PHP służył tylko do komunikacji z bazą danych jako API REST.
Długi wstęp, a teraz do sedna sprawy.
Zwracane typy danych z bazy w PHP
Napiszemy sobie kawałek kodu, który za pomocą biblioteki PDO
, wykona połączenie i pobierze rekord z bazy danych. Dodatkowo korzystając z własności PHP 7 za pomocą declare(strict_types=1);
wymusimy włączenie trybu ścisłego (po więcej informacji zapraszam na stronę PHP).
<?php declare(strict_types=1); $db = new PDO('mysql:host=localhost;dbname=katalog_uslug;charset=UTF8', 'root', ''); $q = $db->query('SELECT `id`,`title`,`id_type`,`date_created` FROM `service`'); $data = $q->fetch(PDO::FETCH_ASSOC); echo "<pre>"; var_dump($data); echo "</pre>";
Po wykonaniu powyższego kodu w moim przypadku uzyskaliśmy następujący wynik:
array(4) { ["id"] => string(2) "12" ["title"] => string(39) "Znajdź lokalizacje dla kodu pocztowego" ["id_type"] => string(1) "4" ["date_created"] => string(19) "2016-10-10 23:44:04" }
Jak możemy zauważyć, każda z pobranych kolumn prezentuje się pod typem string
, gdzie poprawnie kolumna „id” oraz „id_type” powinna zostać zwrócona jako typ int
.
Napiszemy sobie jeszcze kawałek kodu za pomocą, którego potwierdzimy to że podstawowy sterownik do obsługi baz danych MySQL/MariaDB zwraca każdą daną jako typ string.
Poniżej pierwsza funkcja sprawdza czy pobrany „id” równy jest liczbie 12, używając silnego typowania. Natomiast druga funkcja pobierze nam i zwróci „id”.
function isEquel($var, $value) : bool { return $var === $value; } function getId(array $var) : int { return $var['id']; } echo "isEquel = "; var_dump (isEquel($data['id'], 12)); // isEquel = bool(false) echo "<br>getId = " . getId($data);
Uruchamiając ten kawałek kodu okaże się, że przy wywołaniu funkcji isEquel liczba 12 nie jest równa liczbie pobranej z bazy, mimo że też jest wartością 12 (jako string).
W przypadku drugiej funkcji getId dostaniemy błąd:
Fatal error: Uncaught TypeError: Return value of getId() must be of the type integer, string returned […]
W skrócie oznacza to że funkcja powinna zwrócić nam typ int
, a zwraca string
.
Gdzie jeszcze możemy mieć z tym problemy?
Pisząc prostą aplikację typu API REST, pobierając dane np przez Javascript i wyświetlając je w przeglądarce, będzie wszystko ok – do momentu, kiedy to będziemy potrzebować np odwrócenia kolejności wyświetlanych wyników, czy sortowanie względem „id”. Sortując okaże się że JS potraktuje naszą liczbę jako łańcuch znaków, więc sortowanie będzie błędne.
Oczywiście że możemy pisać dodatkowe funkcje po stronie frontu jak i backendu, by dane był zwracane w odpowiednim typie, tylko po co? Jeżeli okaże się że potrzebujemy zwrócić ponad 1000 wierszy i do tego kilka kolumn rzutować do innego typu to taka operacja będzie niepotrzebnie używać nasz lub klienta procesor.
Pobieranie poprawnych typów w PHP z bazy MySQL/MariaDB
Ten krok dla jednych będzie łatwy dla innych troszkę trudniejszy. Postaram się wszystko wyjaśnić, by każdemu wszystko zadziałało.
Na samym początku musimy sobie powiedzieć że w podstawowej konfiguracji PHP do pobierania danych z baz MySQL/MariaDB używa sterownika libmysql. Sterownik ten przesyła dane za pomocą ciągu znaków, więc każda dana nie zależnie czy w bazie jest zdefiniowana jako int lub float i tak zostanie przedstawiona jako string.
By nasz PHP poprawnie pobierał dane z zachowaniem typów potrzebujemy skorzystać z innego sterownika, który nazywa się libmysqlnd. Ten sterownik do komunikacji PHP <> MySQL używa binarnego przesyłania danych.
Na samym początku opiszę co musimy zrobić by poprawnie używać sterownika, ponieważ dla części z Was będzie to wystarczające. Natomiast nie zawsze jest tak łatwo i może okazać się że sterownik MySQLnd nie jest skompilowany, załączony lub w ogóle może nie istnieć w naszym systemie.
PHP wymuszenie używania libmysqlnd z odpowiednim typowaniem
Gdy mamy nasz wcześniejszy kod, wystarczy że napiszemy jedną linijkę ustawiającą specjalny atrybut na wartość false
. Tym atrybutem jest stała PDO::ATTR_EMULATE_PREPARES
.
Uwaga! Jeżeli ustawienie tego atrybutu kończy się błędnem, to niestety nasz PHP nie obsługuje sterownika libmysqlnd. Co za tym idziemy będziemy musieli przejść do następnego kroku, którym jest instalacja oraz konfiguracja.
<?php declare(strict_types=1); $db = new PDO('mysql:host=localhost;dbname=katalog_uslug;charset=UTF8', 'root', ''); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Od tego momentu sterownik MySQLnd zachowa nam typy, a więc cyfra będzie cyfrą, a nie ciągiem znaków. Sprawdźmy to !
$q = $db->query('SELECT `id`,`title`,`id_type`,`date_created` FROM `service`'); $data = $q->fetch(PDO::FETCH_ASSOC); echo "<pre>"; var_dump($data); echo "</pre>"; // WYNIK Z PRZEGLĄDARKI // array(4) { ["id"]=> int(12) ["title"]=> string(39) "Znajdź lokalizacje dla kodu pocztowego" ["id_type"]=> int(4) ["date_created"]=> string(19) "2016-10-10 23:44:04" }
Woow to działa 😉 Oczywiście, od tego momentu wszystkie dane pobrane z bazy będą miały zachowany odpowiedni typ. Teraz możemy przetestować dalszą część wcześniej napisanego kodu.
function isEquel($var, $value) : bool { return $var === $value; } function getId(array $var) : int { return $var['id']; } echo "isEquel = "; var_dump (isEquel($data['id'], 12)); // isEquel = bool(true) echo "<br>getId = " . getId($data); // getId = 12
Jak widać, wszystko działa 😉 Tym razem pisząc API REST i wysyłając dane, po stronie frontu nie będziemy mieli problemy, a dodatkowo obejdziemy się bez zbędnego używania funkcji mapujących typ.
Stwierdziłem że następną część artykułu opisującą proces instalacji sterownika MySQLnd wstawię do nowego wpisu. Przez co zainteresowane osoby zapraszam pod poniższy link:
Instalacja sterownika MySqlnd w PHP
Jeżeli podał Ci się ten artykuł, to pozostaw komentarz, a następnie udostępnij bloga inny 😉
Dziękuję i pozdrawiam!