duży i mały słoń

PHP 7.3 – co nowego?

👋 Dzień dobry.

Nowa wersja PHP v7.3 jest już tuż tuż. 13 grudnia zostanie udostępniona wersja produkcyjna. Zmian jest naprawdę dużo co widać po spisie treści 🙂. Zmiany nie są rewolucyjne, większość z nich to są drobnostki, ale na pewno każdy znajdzie coś dla siebie 👍.

📝 Spis treści:

  1. 💡 Elastyczna składnia HEREDOC / NOWDOC
  2. 💡 Przecinek po ostatnim argumencie w wywołaniach funkcji / metod
  3. 💡 Wsparcie referencji w list()
  4. 💡 json_encode() i json_decode() – rzucanie wyjątków
  5. 💡 Nowa funkcja is_countable()
  6. 💡 Nowe funkcje array_key_first/last()
  7. 💡 Nowa funkcja do haszowania haseł – Argon2id
  8. 💡 Same site cookie
  9. 💡 hrtime() – monotoniczny timer
  10. 💡 Nowa metoda – DateTime::createFromImmutable()
  11. 💡 Nowa funkcja – fpm_get_status()
  12. 💡 Nowe funkcje w GMP
  13. 💡 CompileError – nowy typ błędu
  14. 💡 Aktualizacja PCRE do PCRE2
  15. 💡 MBString – wsparcie pełnego case-mapping i case-folding
  16. 🔧 compact() rzuci Notice gdy napotka niezdefiniowaną zmienną
  17. 🔧 instanceof – literał jako pierwszy operand – brak Fatal error
  18. 🔧 Przeprojektowane narzędzie ext_skel
  19. 🔧 Wyjątki nie wypełnią stanu error_get_last()
  20. 🔧 TypeError zgłosi błędne typy jako int oraz bool
  21. 🔧 Instrukcja continue rzuci Warning wewnątrz switch
  22. 🔧 ArrayAccess nie zrzutuje $offset typu string na int
  23. 🔧 Naprawiono referencje w statycznych własnościach klas
  24. 🔧 Naprawiono odpakowywanie argumentów z kluczami nie int
  25. 🔧 BCMath użyje obsługi błędów PHP
  26. ⚰️ Wsparcie dla BeOS porzucono
  27. ⚰️ Deprecated – image2wbmp()
  28. ⚰️ Deprecated – Stałe case-insensitive
  29. ⚰️ Deprecated – funkcje mb* bez dokumentacji
  30. ⚰️ Deprecated – funkcje szukania w string z argumentem nie string
  31. ⚰️ Deprecated – fgetss() oraz filter string.strip_tags
  32. ⚰️ Deprecated – definiowanie własnej funkcji assert()
  33. ⚰️ Deprecated – FILTER_FLAG_SCHEME_REQUIRED oraz FILTER_FLAG_HOST_REQUIRED
  34. ⚰️ Deprecated – pdo_odbc.db2_instance_name dyrektywa php.ini

💡 Elastyczna składnia HEREDOC / NOWDOC

👉 Znacznik zamykający – nowa linia

Do tej pory, po znaku zamykającym wyrażenie, wymagana była nowa linia:

sql_quote(<<<STR
select *
from table
where 1=1;
STR
);

$data = [<<<DATA
a
b
c
DATA
, 'd e f'];

W nowej wersji nie ma już tego wymogu:

sql_quote(<<<STR
select *
from table
where 1=1;
STR);

$data = [<<<DATA
a
b
c
DATA, 'd e f'];

👉 Wcięcie znacznika zamykającego

W aktualnej wersji PHP znacznik zamykający musi być na początku lini:

class Foo {
    private const SQL = <<<SQL
select *
from table
where 1=1;
SQL;
}

W nowej wersji całe wyrażenie HEREDOC / NOWDOC może być wcięte:

class Foo {
    private const SQL = <<<SQL
    select *
    from table
    where 1=1;
    SQL;
}

Co ważne, wcięcie znacznika zamykającego decyduje o ilości usuniętych białych znaków z łańcucha znaków:

// bez wcięcia znacznika zamykającego
echo <<<END
      a
     b
    c
END;
/*
      a
     b
    c
*/

// wcięcie na 4 spacje
echo <<<END
      a
     b
    c
    END;
/*
  a
 b
c
*/

Ja jestem bardzo zadowolony z tych zmian, często używam HEREDOC / NOWDOC np. do budowania zapytań SQL.

⚠️ Uwaga na składnie nie kompatybilne z nową wersją

Jeśli w starym kodzie masz znacznik zamykający wewnątrz łańcucha znaków, może to spowodować błąd lub łańcuch zostanie źle zinterpretowany. W poniższym kodzie zostanie wyrzucony  ParseError:

$str = <<<FOO
abcdefg
    FOO
FOO;
var_dump($str);
// Parse error: Invalid body indentation level (expecting an indentation level of at least 4) in test.php on line 3

⚠️ Przy tej zmianie radzę zweryfikować czy stary kod działa poprawnie. Polecam do tego Unit Testy.

Więcej w RFC: https://wiki.php.net/rfc/flexible_heredoc_nowdoc_syntaxes

💡 Przecinek po ostatnim argumencie w wywołaniach funkcji / metod

Aktualnie możemy zostawić przecinek po ostatnim elemencie w tablicy oraz w grupowanych namespace. W nowej wersji możemy przecinek zostawić po ostatnim argumencie w wywołaniach:

unset(
    $foo,
    $bar,
    $baz,
);

echo $this->render(
    'index.html',
    compact(
        'title',
        'body',
        'comments',
    )
);

$newArray = array_merge(
    $arrayOne,
    $arrayTwo,
    [
        'foo',
        'bar'
    ],
);

$closure = function () {};
$closure(
    1,
    false,
);

⚠️ Co ważne, dotyczy to tylko wywołań a nie deklaracji!

Jeśli dbasz o ładną historię w systemie kontroli wersji, to na pewno będziesz zadowolony 🙂

Więcej informacji:

💡 Wsparcie referencji w list()

Używając operatora list() nie mogłeś użyć referencji. Teraz jest to możliwe:

$array = [1, 2];
list($c, &$d) = $array;
[$a, &$b] = $array;<br>

Kod jest poprawny i działa jak należy w v7.3 👍

Więcej informacji:

💡 json_encode() i json_decode() – rzucanie wyjatków

Gdy wywołasz json_decode() z niepoprawnym JSON funkcja zwróci null. Co ciekawe, gdy wywołasz ją z  poprawnym JSON który zawiera tylko null, funkcja również zwróci null. Jedyną metodą by sprawdzić czy wystąpił błąd podczas dekodowania jest odwołanie się do funkcji json_last_error() lub json_last_error_msg().

W v7.3 to się zmieni. Ekipa PHP wprowadziła flagę JSON_THROW_ON_ERROR dzięki której funkcja json_encode() oraz json_decode() będą rzucać wyjątek JsonException gdy coś pójdzie nie tak. Co ważne, kod wyjątku będzie zgodny z kodem zwracanym przez funkcję json_last_error(), a wiadomość zgodna z wiadomością zwracaną przez json_last_error_msg().

try {
    $decoded = json_decode("{ 'invalid' : 'json' }", false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
    echo $e->getCode(); // 4
    echo $e->getMessage(); // Syntax error
}

Więcej informacji:

💡 Nowa funkcja is_countable()

W wersji v7.2 dodano Warning które jest wyrzucany podczas próby wywołania count() na czymś co nie jest policzalne, np. na instancji stdClass. By temu zapobiec należy zweryfikować czy rzecz jest policzalna, jednak nie było do tego odpowiedniej funkcji, więc warunek jest złożony:

if (is_array($foo) || $foo instanceof Countable) {
    echo count($foo);
}

W v7.3 wystarczy użyć is_countable():

if (is_countable($foo)) {
    echo count($foo);
}

Więcej informacji:

💡 Nowe funkcje array_key_first/last()

Funkcje te zostały dodane by ułatwić uzyskiwanie kluczy ostatniego i pierwszego elementu z tablicy. W v7.2 trzeba modyfikować wewnętrzny wskaźnik tablicy używając np. end() / current():

$array = [
  1 => 'a',
  5 => 'm',
  10 => 'z'
];

reset($array);
echo key($array); // 1;
end($array);
echo key($array); // 10

W v7.3 możesz użyć dedykowanych funkcji do tego, co ważne, nie zmieniają wskaźnika wewnątrz tablicy:

$array = [
  1 => 'a',
  5 => 'm',
  10 => 'z'
];

echo array_key_first($array); // 1
echo array_key_last($array); // 10

Więcej informacji:

💡 Nowa funkcja do haszowania haseł – Argon2id

Algorytm Argon2 ma trzy odmiany:

  • Argon2i,
  • Argon2d,
  • Argon2id

Ta ostatnia jest teraz zalecaną odmianą w wersji roboczej specyfikacji IEFT. W nowej wersji zostaje wsparcie poprzez funkcje password_* oraz stałą PASSWORD_ARGON2ID:

password_hash('password', PASSWORD_ARGON2ID, ['memory_cost' => 1<<17, 'time_cost' => 4, 'threads' => 2]);

var_dump(password_get_info('$argon2id$v=19$m=1024,t=2,p=2$ZUhOUVczSHpZRDBDU2ZBRA$k/vI1wKP4s0ecJIpUybRfgBeo3as1PhIV1Od6PvOEFA'));

/*
array(3) {
  ["algo"]=>
  int(3)
  ["algoName"]=>
  string(8) "argon2id"
  ["options"]=>
  array(3) {
    ["memory_cost"]=>
    int(1024)
    ["time_cost"]=>
    int(2)
    ["threads"]=>
    int(2)
  }
}
*/

Więcej informacji:

Jest to póki co proponowany standard sieci web, ale widać chęć jego adopcji w przeglądarkach, językach programowania czy frameworkach. Zapobiega atakom CSRF. Metoda polega na ustawieniu odpowiedniej flagi w ciastku HTTP. Ciastka są ustawiane przez serwer za pomocą nagłówka Set-Cookie, nagłówek ten może mieć kilka flag:

Set-Cookie: foo=bar; path=/; domain=example.com; HttpOnly

Nowa flaga to SameSite która może przyjąć dwie wartości, Strict oraz Lax:

Set-Cookie: foo=bar; path=/; domain=example.com; HttpOnly; SameSite=Lax

Zarówno Lax jak i Strict kompletnie blokują ataki CSRF 👍. Prosto i skuteczne. Czym się różnią oba parametry? Strict jak sama nazwa mówi jest bardziej restrykcyjny. Załóżmy, że jesteś zalogowany do serwisu https://www.allegro.pl. Gdyby ten serwis miał ustawioną flagę na Strict, to po kliknięciu w link, który umieściłem w tym zdaniu zostałbyś przekierowany do strony, lecz byłbyś wylogowany! W trybie Lax jest mniej restrykcyjny. Pozwala on na wczytanie zawartości pliku cookie pod warunkiem, że żądanie jest wykonane przy użyciu bezpiecznej metody, tzn. GET, HEAD, OPTIONS, TRACE. Ataki CSRF są wykonywane zazwyczaj przy użyciu metod POST, PUT, DELETE. Gdyby jednak twoja strona akceptowała żądania niebezpieczne metodą GET, np:

GET http://www.example.com/api/deleteUser?id=1

to zdecydowanie zalecam użycia trybu Strict.

Ekipa PHP zmodyfikowała cztery funkcje:

  • setcookie
  • setrawcookie
  • session_set_cookie_params
  • session_get_cookie_params

Pierwsze trzy funkcje mają bardzo zbliżoną sygnaturę. Sygnatura funkcji setcookie w wersji v7.2 wygląda tak:

bool setcookie(string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [ , bool $secure = FALSE [ , bool $httponly = FALSE ]]]]]] )

Straszny bajzel z tym parametrami! Więc ludzie odpowiedzialni za PHP postanowili, że nie będą wprowadzać kolejnego argumentu. Zaimplementowali alternatywną wersją funkcji:

bool setcookie ( array $options )

Opcje jakie możesz teraz przekazać są następujące:

  • name,
  • value,
  • expire,
  • path,
  • domain,
  • secure,
  • httponly,
  • samesite.

Pozostałe sygnatury po zmianach:

bool setrawcookie ( array $options )
void session_set_cookie_params ( array $options )

Sygnatura funkcji session_get_cookie_params się nie zmieniła. Dodany został nowy klucz w tablicy zwracanej:

array(
  "lifetime" => 0,
  "path" => "/",
  "domain" => "example.org",
  "secure" => true,
  "httponly" => true,
  "samesite" => "Strict" // nowy klucz
)

Więcej informacji:

💡 hrtime() – monotoniczny timer

Czy wiesz, że funkcja microtime() ma pewne ograniczenia? Dość często wylicza się czas wykonania kodu właśnie przy pomocy microtime(). Na początku kodu deklarujesz zmienną która zawiera czas uruchomienia skryptu a na końcu deklarujesz kolejną zmienną, tym razem czas zakończenia. Wystarczy, że obliczysz różnicę i otrzymasz czas wykonania. Kod:

$startTime = microtime();
/**
 * wykonaj jakiś skomplikowany kod
 */
$endTime = microtime();
$elapsed = $endTime - $startTime;
var_dump($elapsed);

Problem polega na tym, że jeśli czas systemowy zmieni się podczas wykonywania skryptu to czas wykonywania skryptu będzie niepoprawny. Dlatego zaimplementowano funkcję hrtime(). Dzięki niej czas wykonywania skryptu będzie super dokładny. Użycie:

$startTime = hrtime();
/**
 * wykonaj jakiś skomplikowany kod
 */
$endTime = hrtime();
$elapsed = $endTime - $startTime;
var_dump($elapsed);

Więcej informacji:

💡 Nowa metoda – DateTime::createFromImmutable()

Metoda ta jest lustrzana do metody DateTimeImmutable::createFromMutable(). Więcej informacji:

💡 Nowa funkcja – fpm_get_status()

Zwraca ona aktualny status menadżera procesów fastcgi. Więcej informacji: https://github.com/php/php-src/commit/140def4ac712acac6e2561f65e1b72f8efad8717

💡Nowe funkcje w GMP

👉 gmp_binomial()

Wylicza Symbol Newtona. Więcej informacji: https://pl.wikipedia.org/wiki/Symbol_Newtona. Commit: https://github.com/php/php-src/commit/7fea79675cfb064726f3fc7845e2c2bb1f247ea5

👉 gmp_lcm()

Wylicza najmniejsza wspólną wielokrotność. Więcej informacji: https://pl.wikipedia.org/wiki/Najmniejsza_wspólna_wielokrotność. Commit: https://github.com/php/php-src/commit/a1d36a1157bd88afd64119be059812dd46c4fb2d

👉 gpm_perfect_power()

Sprawdza czy dana liczba jest perfect power. Więcej info: https://en.wikipedia.org/wiki/Perfect_power. Commit: https://github.com/php/php-src/commit/10a336d3d062fdfa0133b22fb4f79852ec939ea5

👉 gmp_kronecker()

Wylicza Symbol Kroneckera. Więcej informacji: https://pl.wikipedia.org/wiki/Symbol_Kroneckera. Commit: https://github.com/php/php-src/commit/fc80114a481e1e7fc8c7be775a2cce683db9c107

💡 CompileError – nowy typ błędu

Ekipa PHP dodała nowy błąd do aktualnie dostępnych. Dziedziczy po nim ParseError. Póki co bardzo niewielka ilość błędów kompilacji rzuci CompileError zamiast generować błąd fatal. Dotyczy to tylko funkcji token_get_all() w trybie TOKEN_PARSE oraz eval(). W przyszłości więcej błędów będzie rzucać CompileError.

try {
    token_get_all('<?php declare(encoding=[]); ?>', TOKEN_PARSE);
} catch (\CompileError $error) {
    echo $error->getMessage(); // Encoding must be a literal
}

Więcej informacji https://github.com/php/php-src/commit/d04917c7b361fd07e098fe29ae931fb6fac1d9e0

🔧 Aktualizacja PCRE do PCRE2

PHP aktualnie bazuje na PCRE w wersji pierwszej które jest dość stare. Dlatego postanowiono o aktualizacji do najnowszej wersji, PCRE2 która została wydana w 2015. Całość zmian jest “pod maską” ale kilka rzeczy się zmieni:

  • modyfikator S nie ma wpływu na zachowanie, wszystkie wyrażenia regularne są teraz dokładniej analizowane, więc ta flaga jest zawsze włączona,
  • modyfikator X również nie ma wpływu na zachowanie, PCRE2 domyślnie korzysta z tej flagi więc zespół PHP ustawił ją zawsze włączoną,
  • PCRE2 korzysta z nowszego Unicode (v10 w PCRE2 vs v7 w PCRE) więc niektóre wyrażenia mogą się inaczej zachowywać,
  • ⚠️ PCRE2 jest bardziej dokładne, więc niektóra wyrażenie które były zgodne z poprzednią implementacją, teraz mogą nie działać jak należy.

Więcej informacji:

🔧 MBString – wsparcie pełnego case-mapping i case-folding

W stosunku do do prostego case-mapping, jego pełna odmiana może zmienić długość łańcucha znaków. Przykład:

mb_strtoupper("Straße")
// "STRAßE" - PHP v7.2
// "STRASSE" - PHP v7.3

Różne typy case-mapping oraz case-folding są dostępne przez mb_convert_case():

  • MB_CASE_LOWER (używane domyślnie przez mb_strtolower),
  • MB_CASE_UPPER (używane domyślnie przez mb_strtoupper),
  • MB_CASE_TITLE,
  • MB_CASE_FOLD,
  • MB_CASE_LOWER_SIMPLE,
  • MB_CASE_UPPER_SIMPLE,
  • MB_CASE_TITLE_SIMPLE,
  • MB_CASE_FOLD_SIMPLE (używane domyślnie  przez operacje case-insensitive)
function toCases($str) {
    echo "String: $str\n";
    echo "Lower: ", mb_convert_case($str, MB_CASE_LOWER), "\n";
    echo "Lower Simple: ", mb_convert_case($str, MB_CASE_LOWER_SIMPLE), "\n";
    echo "Upper: ", mb_convert_case($str, MB_CASE_UPPER), "\n";
    echo "Upper Simple: ", mb_convert_case($str, MB_CASE_UPPER_SIMPLE), "\n";
    echo "Title: ", mb_convert_case($str, MB_CASE_TITLE), "\n";
    echo "Title Simple: ", mb_convert_case($str, MB_CASE_TITLE_SIMPLE), "\n";
    echo "Fold: ", mb_convert_case($str, MB_CASE_FOLD), "\n";
    echo "Fold Simple: ", mb_convert_case($str, MB_CASE_FOLD_SIMPLE), "\n";
    echo "\n";
}

toCases("ß");
toCases("ff");
toCases("İ");

/*
String: ß
Lower: ß
Lower Simple: ß
Upper: SS
Upper Simple: ß
Title: Ss
Title Simple: ß
Fold: ss
Fold Simple: ß

String: ff
Lower: ff
Lower Simple: ff
Upper: FF
Upper Simple: ff
Title: Ff
Title Simple: ff
Fold: ff
Fold Simple: ff

String: İ
Lower: i̇
Lower Simple: i
Upper: İ
Upper Simple: İ
Title: İ
Title Simple: İ
Fold: i̇
Fold Simple: İ
*/

⚠️ Przy tej zmianie radzę zweryfikować czy stary kod działa poprawnie. Polecam Unit Testy do tego.

Więcej informacji:

🔧 compact() rzuci Notice gdy napotka niezdefiniowaną zmienną

Przed v7.3 funkcja compact() w żaden sposób nie informowała o fakcie przekazania nazwy niezadeklarowanej zmiennej. W przypadku literówki jest to bardzo przydatna rzecz. Spójrz na ten przykład, który wyrzuci Notice w v7.3:

$foo = 'bar';

$baz = compact('foz'); // Notice: compact(): Undefined variable: foz

⚠️ Przy tej zmianie radzę zweryfikować czy stary kod działa poprawnie. Polecam Unit Testy do tego.

Więcej informacji:

🔧 instanceof – brak Fatal error w przypadku literału

Do wersji v7.2 operator instanceof działał tylko z obiektami, w v7.3 naprawili to i teraz możesz wykonać instanceof na dowolnym typie, w przypadku gdy nie będzie to obiekt, zwróci false:

php > var_dump(false instanceof \stdClass);
bool(false)
php > var_dump([] instanceof \stdClass);
bool(false)
php > var_dump('test' instanceof \stdClass);
bool(false)
php > var_dump(1 instanceof \stdClass);
bool(false)

Więcej informacji:

🔧 Przeprojektowane narzędzie ext_skel

Narzędzie to z pewnością przyda Ci się jeśli planujesz stworzyć własne rozszerzenie do PHP. Każde rozszerzenie składa się z mnóstwa plików, wiele z nich wygląda bardzo podobnie i mają zbliżoną treść, wręcz duplikacje w niektórych miejscach. By ułatwić pracę przy tych plikach powstało narzędzie ext_skel. We wcześniejszych wersjach PHP narzędzie to było napisane w bash z kilkoma zależnościami. W v7.3 całość została przepisane w PHP + zero zależności. By tworzyło się rozszerzenia łatwiej 👌 Więcej info:

🔧 Wyjątki nie wypełnią stanu error_get_last()

Wyjątki wyrzucane przez automatyczną konwersję Warning w trybie EH_THROW już nie wypełniają stanu error_get_last(). Teraz działają w ten sam sposób co wyjątki rzucane manualnie.

try {
    new DateTime('lalala');
} catch (\Throwable $exception) {
    var_dump(error_get_last());
    /*
    array(4) {
      ["type"]=>
      int(2)
      ["message"]=>
      string(128) "DateTime::__construct(): Failed to parse time string (lalala) at position 0 (l): The timezone could not be found in the database"
      ["file"]=>
      string(53) "/Users/damiandziaduch/Downloads/php-7.3.0RC4/test.php"
      ["line"]=>
      int(3)
    }
*/
}
try {
	new DateTime('lalala');
} catch (\Throwable $exception) {
	var_dump(error_get_last()); // NULL
}

Więcej:

🔧 TypeError zgłosi błędne typy jako int oraz bool

Gdy w pliku PHP masz zadeklarowane strict_types to musisz sam zadbać o typy zmiennych. Załóżmy, że masz metodę która przyjmuję argument typu int. Gdy przekażesz argument type string, język wyrzuci TypeError. W v7.2 w przypadku gdy twój kod oczekuje int albo bool, wyjątek zgłosi, że oczekuje integer lub boolean :

// TypeError: Argument 1 passed to Foo::__construct() must be of the type integer, string given

W v7.3 poprawiono to i teraz zgłasza int lub bool:

// TypeError: Argument 1 passed to Foo::__construct() must be of the type int, string given

Więcej:

🔧 Instrukcja continue rzuci Warning wewnątrz switch

Jeśli w kodzie masz pętlę np. while w której jest struktura switch, a do tego wewnątrz switch użyjesz continue by przejść do kolejnej iteracji w pętli, to twój kod był poprawny w v7.2, ale działał nie prawidłowo. continue wewnątrz switch  działa tak samo break, przerywa dalsze przetwarzanie switch i nie przechodzi do kolejnej iteracji w pętli zawierającej switch. Wynika to z faktu iż switch jest uważany przez język za pętlę. Więc by przejść do kolejnej iteracji należy użyć continue 2. Zespół PHP wyszedł temu na przeciw i wyrzuci Warning w tym przypadku:

while ($foo) {
    switch ($bar) {
        case "baz":
            continue;
            // Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"?
    }
}

⚠️ Przy tej zmianie radzę zweryfikować czy stary kod działa poprawnie. Polecam Unit Testy do tego.

Więcej:

🔧 ArrayAccess nie zrzutuje $offset typu string na int

Gdy obiekt implementuje interfejs ArrayAccess i odwołujesz się do niego poprzez $foo['123'] to zostanie na nim wywołana metoda ->offsetGet(123) w przypadku v7.2. Zwróć uwagę, że string 123 został rzucony na int. W v7.3 zostało to poprawione więc kod prawidłowo wywoła ->offsetGet('123'); Przykład kodu:

class Foo implements \ArrayAccess
{
    public function offsetGet($offset) {
        var_dump($offset); // '123' w v7.2, 123 w v7.3
    }

    public function offsetExists($offset) {
    }

    public function offsetSet($offset, $value) {
    }

    public function offsetUnset($offset) {
    }
}

$foo = new Foo();
$foo['123'];
$foo[123];

⚠️ Przy tej zmianie radzę zweryfikować czy stary kod działa poprawnie. Polecam Unit Testy do tego.

Więcej informacji https://github.com/php/php-src/commit/30156d588c07e26d4e752ddb62344e96854d4773#diff-ff4e2dc4962dc25a1512353299992c8dL19

🔧 Naprawiono referencje w statycznych własnościach klas

Statyczne własności są współdzielone między instancjami klasy oraz między instancjami klasami potomnymi. Wyjątkiem jest klasa potomna która bezpośrednio nadpisze własność. Co ciekawe, w v7.2 jest dość ciekawy błąd, który pozwala zepsuć współdzielenie dzięki użyciu referencji:

class Foo
{
    public static $x = 0;
}

class Bar extends Foo
{
}

$y = 1;
Bar::$x = &$y;

var_dump(Foo::$x); // int(0)
var_dump(Bar::$x); // int(1)

W v7.3 zostało to naprawione, obie klasy będę miały wartość int(1) 👍.

⚠️ Przy tej zmianie radzę zweryfikować czy stary kod działa poprawnie. Polecam Unit Testy do tego.

Więcej informacji:

🔧 Naprawiono odpakowywanie argumentów z kluczami nie int

Okazuje się, że w PHP v7 – v7.2 był błąd dzięki któremu ten kod:

function foo(...$args)
{
    var_dump($args);
}

function gen()
{
    yield 1.23 => 123;
}

foo(...gen()); // array(1) { [0] => int(123) }

w pełni działał. Zostało to naprawione i rzucany jest teraz wyjątek:

// PHP Fatal error:  Uncaught Error: Cannot unpack Traversable with non-integer keys

⚠️ Przy tej zmianie radzę zweryfikować czy stary kod działa poprawnie. Polecam Unit Testy do tego.

Więcej informacji:

🔧 BCMath użyje obsługi błędów PHP

Wcześniej niektóre ostrzeżenia rzucane przez funkcje BCMath były wypisane bezpośrednio do stderr. Przykład:

var_dump(bcpowmod(1, 1.2, 1, 1));
/*
v7.2:
bc math warning: non-zero scale in exponent

v7.3:
PHP Warning:  bcpowmod(): non-zero scale in exponent
*/

Więcej informacji:

⚰️ Wsparcie dla BeOS porzucono

System ten został wykupiony przez Palm w 2001. Od tego czasu nie było nowej wersji systemu. Więcej:

⚰️ Deprecated – image2wbmp()

W rozszerzeniu gd do PHP są dwie bardzo zbliżone do siebie funkcje, image2wbmp() (http://php.net/manual/en/function.image2wbmp.php) oraz imagewbmp() (http://php.net/manual/en/function.imagewbmp.php). Robią one praktycznie to samo, więc ekipa PHP postanowiła zostawić tylko jedną. W 7.3 image2wmbp zostaje oznaczona jako deprecated, w kolejnych wersjach zostanie usunięta.

Więcej informacji:

⚰️ Deprecated – Stałe case-insensitive

PHP aktualnie wspiera zmienne case-sensitive oraz case-insensitive. Te drugie można zadeklarować używając funkcji define() z trzecim parametrem true (http://php.net/manual/en/function.define.php). By ujednolicić oraz pozbyć się dziwnych problemów typu:

define('foo', 42, true);
var_dump(FOO); // int(42);
define('FOO', 24);
var_dump(FOO); // int(24)

więc zespół PHP oznaczył stałe case-insensitive jako deprecated. Więc teraz język będzie wyrzucał takie oto wiadomości:

define('FOO', 42, true); // Deprecated: define(): Declaration of case-insensitive constants is deprecated
var_dump(FOO); // Ok!
var_dump(foo); // Deprecated: Case-insensitive constants are deprecated. The correct casing for this constant is "FOO"

W kolejnych wersjach stałe te zostaną usunięte.

Więcej informacji:

⚰️ Deprecated – funkcje mb* bez dokumentacji

Lista funkcji:

  • mbregex_encoding,
  • mbereg,
  • mberegi,
  • mbereg_replace,
  • mberegi_replace,
  • mbsplit,
  • mbereg_match,
  • mbereg_search,
  • mbereg_search_pos,
  • mbereg_search_regs,
  • mbereg_search_init,
  • mbereg_search_getregs,
  • mbereg_search_getpos,
  • mbereg_search_setpos.

Wszystkie te funkcje to aliasy do funkcji z prefiksem mb_, np. mberegi to alias do mb_eregi.

Więcej:

⚰️ Deprecated – funkcje szukania w `string` z argumentem nie `string` int

Dotyczy to funkcji:

  • strpos,
  • strrpos,
  • stripos,
  • strripos,
  • strstr,
  • strchr,
  • strrchr,
  • stristr.

Załóżmy, że w string Ala ma 10 kotów chcesz znaleźć 10, ale przekazujesz 10 jako int. Niestety to nie zadziała, ponieważ powyższe funkcję rzutują na int każdy argument, który nie jest typu string. Następnie dla wynikowej wartości pobierają znak z tablicy ASCII. 10 w ASCII to znak nowej lini. Sam tego nie wiedziałem dopóki nie zacząłem pisać tego wpis 🤔 Kod w 7.2:

$str = "Ala ma 10 kotów";
var_dump(strpos($str, "10")); // int(7)
var_dump(strpos($str, 10));   // bool(false)

Kod w 7.3:

$str = "Ala ma 10 kotów";
var_dump(strpos($str, "10"));
// int(7)
var_dump(strpos($str, 10));
// Deprecated: strpos(): Non-string needles will be interpreted as strings in the future. Use an explicit chr() call to preserve the current behavior in php shell code on line 1
// bool(false)

Więcej:

⚰️ Deprecated – fgetss() oraz filter string.strip_tags

Obie konstrukcje umożliwiają użycie funkcjonalności z strip_tags() podczas streamingu. Jak wiadomo strip_tags() działa niezbyt dokładnie, dlatego zespół PHP postanowił oznaczyć następujące twory jako deprecated:

  • funkcja fgetss()
  • funkcja gzgetss()
  • metoda SplFileObject::fgetss()
  • filter streamu string.strip_tags()

Więcej:

⚰️ Deprecated – definiowanie własnej funkcji assert()

W PHP jest wbudowana funkcja assert(). Jest ona specjalnie traktowana przez kompilator, tzn gdy ustawisz w zend.assertions=-1 to kompilator tą funkcję po prostu pominie. Przydatne gdy korzystasz z tej funkcji a kod wrzucasz na produkcję. Co ciekawe, możesz sobie zdefiniować własną funkcję assert() w nieglobalnym namespace np foo\assert(). A w samym PHP jest błąd, po wyłączeniu wbudowanej funkcji assert(), wszystkie zdefiniowane przez użytkownika również zostaną wyłączone 🙃 Dlatego w 7.3 podczas definiowania własnej funkcji assert() zostaniemy poinformowani, że jest to Deprecated:

namespace Foo {
    function assert() {
        echo 'foobar';
    }
}
// Deprecated: Defining a custom assert() function is deprecated, as the function has special semantics in php shell code on line 1

Więcej:

⚰️ Deprecated – FILTER_FLAG_SCHEME_REQUIRED oraz FILTER_FLAG_HOST_REQUIRED

Od wersji 5.2.1 FILTER_VALIDATE_URL implikuje oba filtry, więc są one zbędne. Dodatkowo tworzą wrażenie, że walidacja schematu / host może zostać pominięta, co jest nieprawdą. Od 7.3 podczas próby ich użycia otrzymasz ostrzeżenie Deprecated:

var_dump(filter_var('//example.com/', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED));
// Deprecated: filter_var(): explicit use of FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED is deprecated in php shell code on line 1
bool(false)
var_dump(filter_var('http://', FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED));
// Deprecated: filter_var(): explicit use of FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED is deprecated in php shell code on line 1
bool(false)

⚰️ Deprecated – pdo_odbc.db2_instance_name dyrektywa php.ini

Od wersji 5.1.1 ta dyrektywa została oznaczona jako Deprecated w manualu. Okazuje się jednak, że w kodzie nie była oznaczona jako Deprecated (a to psikus 🤔) więc zostało to “ujednolicone”. Dyrektywa ta modyfikuje zmienną środowiskową DB2INSTANCE dla systemów nie-Windows. Jak wiadomo zmiana czegoś globalnego to nie jest dobry pomysł…

Więcej:

Podsumowanie.

Uff to chyba najdłuższy wpis na blogu. Poświęciłem mu dużo pracy. Gdy piszę to podsumowanie jest to 106 wersja tego wpisu 😊.

Czy warto zaktualizować do v7.3? Jasne, że tak. Nie ma aż tak dużo zmian które nie są kompatybilne wstecz, większość starego kodu powinna bez problemu działać. Przy zmianach które mogą robić problemy dodałem ostrzeżenia. PhpStorm już jest gotowy na zmiany: https://www.jetbrains.com/phpstorm/nextversion/#php-73-support.

Mam nadzieję, że spodobał Ci się ten post. Starałem się by wszystkie zmiany wytłumaczyć oraz dodać stosowane źródła. Nie chciałem by ten wpis był tylko tłumaczeniem changeloga 🙈.

Dziękuję za poświęcony czas na przeczytanie tego potworka. Proszę udostępnij go, jestem pewny, że wiele osób będzie zainteresowanych tą tematyką 👌.

21 thoughts on “PHP 7.3 – co nowego?”

          1. RSS powinien działać 🙂.

            Planuje subskrypcje e-mail. Powiadomienia bardzo fajny pomysł 👍.

            Niespodziewałem się takiego odzewu 🙈. Serdecznie dziekuje za wszystkie komentarze 😊

        1. Cześć Papa Czarny.

          Powiadomienia Desktop (push) są już dostępne. Kliknij proszę w ikonkę dzwonka w prawym dolnym rogu.

          Pozdrawiam 🙂

    1. Ups, dzięki wielkie Sebastian, już naprawione.

      Nie ma za co. Jak v7.4 będzie blisko to na pewno napiszę kolejny tekst 🙂

  1. Uruchamiałeś kod w “instanceof – brak Fatal error w przypadku literału”? To nie rzuci fatal errora. Źle zrozumiałeś poprawkę. Prośba o poprawę artykułu.

    1. Cześć, dzięki za opinię.

      Tak, sprawdzałem czy jest Fatal i faktycznie jest:

      ➜ ~ php -v
      PHP 7.1.23 (cli) (built: Oct 11 2018 16:19:26) ( NTS )
      Copyright (c) 1997-2018 The PHP Group
      Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
      with Xdebug v2.6.0, Copyright (c) 2002-2018, by Derick Rethans
      with Zend OPcache v7.1.23, Copyright (c) 1999-2018, by Zend Technologies
      ➜ ~ php -a
      Interactive shell

      php > var_dump(false instanceof \stdClass);
      PHP Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

      Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

      php > var_dump([] instanceof \stdClass);
      PHP Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

      Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

      php > var_dump('test' instanceof \stdClass);
      PHP Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

      Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

      php > var_dump(1 instanceof \stdClass);
      PHP Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

      Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

      1. Tego akurat dotyczy poprawka, więc dobrze, że rzuca błędem, choć nie bardzo rozumiem po co ktoś miałby robić takie porównania 🙂 Nie dokładnie napisałem swój pierwszy komentarz. Chodziło mi o kod z użyciem funkcji “array_filter”. Tam jest porównanie zmiennej, więc wszystko jest OK, a przykład może wprowadzać w błąd.

        1. Witaj ponownie PHP.

          Masz rację:
          php > $a = null;
          php > var_dump($a instanceof \stdClass);
          bool(false)
          php > var_dump(null instanceof \stdClass);
          PHP Fatal error: instanceof expects an object instance, constant given in php shell code on line 1

          Mój błąd 😉 Postaram się dzisiaj poprawić.

          Dziękuję za wyłapanie tego 👍

  2. Niestety po przejściu z 7.2 na 7.3 padło mi kodowanie polskich znaków. Stronę mam w ISO-8859-2 użycie ini_set(“default_charset”,”ISO-8859-2″); nie pomaga. Wróciłem do 7.2 🙁

    1. Cześć Tomek.

      Co Ci się wyświetla zamiast polskich znaków? Może czas rozważyć przejście na UTF-8?

      Dzięki za komentarz 🙂

  3. Pomogło dopisanie default_charset=”ISO-8859-2″ w pliku .user.ini mam polskie znaki w PHP 7.3.

Comments are closed.