PHP to język używający typowania dynamicznego, inaczej zwanego słabym. W skrócie oznacza to tyle, że typ zmiennej jest ustalany w czasie działania programu, a sam interpreter spróbuje czasem dostosować go według pewnych zasad. Przykładowe działanie w języku PHP dla początkujących może być małym zdziwieniem: "3" + null + 2
w języku PHP równa się 5.
Tego trzeba się nauczyć i być przede wszystkim świadomym. Gorzej wygląda to w przypadku takiej samej transformacji typów dla argumentów funkcji. Rezultat także może być nieoczekiwany, ale już nieco trudniej go kontrolować.
<?php
function calculate(int $num1, int $num2): void
{
echo $num1 + $num2;
}
calculate(true, false);
Wynik z takich danych wejściowych to 1. Wartości typu bool
zostały transformowane do typu int
, gdzie odpowiednio true = 1
, a false = 0
.
<?php
declare(strict_types=1);
function calculate(int $num1, int $num2): void
{
echo $num1 + $num2;
}
calculate(true, true);
Na całe szczęście od wersji 7 PHP można wymusić restrykcyjne traktowanie typów dla takiego przypadku. Jedyną różnicą między dwoma blokami kodu jest deklaracja strict_types=1
. Tym razem skończy się to fatal errorem, co jest jak najbardziej zasadne.
Fatal error: Uncaught TypeError: Argument 1 passed to calculate() must be of the type integer, boolean given, called in […][…] on line 10 and defined in […][…]:5
Stack trace: 0 […]: calculate(true, false) 1 {main} thrown in […][…] on line 5
Nie taka nowość jakby się wydawało…
Mam wrażenie, że wszyscy programiści o tym wiedzą. Zazwyczaj śledzi się nowości wchodzące wraz z nową wersją, a szczególnie przejście z PHP5.6 na PHP7.0. Tak więc pewnie nie wielka część z Was dowie się czegoś istotnego. Cel jest jednak inny – przekonać do używania tego mechanizmu.
Patrząc na kod open source raczej rzadko można doszukać się używania tej funkcjonalności. Rozumiem starsze rozwiązania, które są tylko wspierane i dostosowywane najniższym kosztem do kolejnych wersji języka, ale próżno szukać chociażby w najnowszej wersji Symfony, czy Laminas. Oczywiście zakładam, że programiści są świadomi i nie pozwalają sobie na tego rodzaju przypadkowe działanie kodu, ale po co samemu się pilnować skoro możemy to wymusić.
Szczególnie trudne do rozpoznania przypadki to te, w których dane wejściowe pochodzą od użytkownika. Jasna sprawa, że trzeba je poddać sanityzacji i walidacji, ale to i tak może nie wystarczyć. A przecież to jedna z najważniejszych zasad: znaj wejście i wyjście.
Kiedy ja zaczynałem używać restrykcyjnego typowania to bardzo przeszkadzał mi pewien fakt. Niestety nie da się włączyć tego globalnie dla całego projektu. No a przynajmniej nie tak łatwo – są pewne obejścia, ale nie polecam takich rozwiązań, dlatego też nie będę ich prezentował. Wspominam o nich tylko dla kompletności wpisu. Zresztą teraz już rozumiem, że nawet gdyby natywnie istniała taka opcja to mało komu udałoby się z niej skorzystać – a na pewno nie w tych projektach, w których wykorzystywane są zewnętrzne zależności. Czyli pewnie w większości, a we wszystkich tych, w których ja brałem udział.
Wynika z tego więc, że nawet startując z nowym projektem, nie można zastosować restrykcyjnego podejścia dla całego projektu, bo biblioteki nie są na to gotowe. Co nie oznacza, że nie są pisane w sposób restrykcyjny, ale kto wie skoro interpreter tego nie pilnuje.
PhpStorm jak zawsze ogromne ułatwienie
Może się więc okazać, że dodawanie tego w każdym pliku jest uciążliwe. Ja znalazłem na to rozwiązanie, którym podzielę się poniżej. Teraz już nie ma możliwości, że zdarzy mi się zapomnieć, a Wam odpada jeden argument przeciw temu mechanizmowi. W końcu w dzisiejszych czasach mamy IDE. Ja korzystam z PhpStorm, więc pokażę jak załatwić w nim sprawę. Wystarczy wejść w File > Settings > Editor > File and Code Templates
i w szablonach wrzucić jedną linię: declare(strict_types=1);
dla klas, interfejsów i traitów. Na screenie i w bloku z kodem prezentuję mój template dla klasy:
<?php
declare(strict_types=1);
#if (${NAMESPACE})
namespace ${NAMESPACE};
#end
class ${NAME}
{
}
Teraz przy tworzeniu nowej klasy nie trzeba pamiętać o ręcznym wrzucaniu deklaracji. Konfiguracja jest globalna, więc uważajcie z tym w starych projektach, gdzie typowanie jest słabe. Tyle, że tworząc nowy plik i tak możecie narzucić konwencję – mały plus tego że można tylko dla niektórych plików używać.
Warto kontrolować dane wejściowe do każdej metody. To zawsze była biznesowa przewaga Javy i innych języków silnie typowanych nad PHP, więc warto ją zniwelować. Takie przesłanie płynie z tego wpisu, a dodatkowo wrzucam Wam rozwiązanie, które mi ułatwia to osiągnąć. Przy okazji tak samo zalecam używanie w testach asercji assertSame()
zamiast assertEquals()
– jako, że typ jest istotny w całej układance.
Jak zawsze piona mordo za wpis!