PHP7

25 stycznia 2017 10min.

Co nowego w PHP7?

Mimo wciąż toczącej się walki, w której to przeciwnicy PHP wręcz krzywią się na mówienie o nim „język programowania”, to wciąż jeden z bardziej popularnych języków na świecie. Dlatego wydanie wersji oznaczonej numerem 7 na pewno jest dużym wydarzeniem w środowisku, a jednocześnie daje trochę argumentów zwolennikom. Istotnie, wprowdzono sporo zmian, które na pewno poprawiają jakośc życia programistom. Zastanawialiście sie, co pojawiło się w nowej wersji? Jeśli tak, zaparzajcie kawę (albo, jak kto woli, herbatę) i czytajcie dalej.

Zawartość artykułu:

  1. Jak wzrosła szybkość PHP 7?
  2. Jakie nowości w PHP 7?
  3. Czym jest “Kosmiczny” operator <=>?
  4. Czym jest Null Coalesce Operator (??)
  5. Czym są klasy anonimowe?
  6. Jak zmienia się grupowanie deklaracji?
  7. Łap błędy!
  8. Jak wyglądają wyrażenia po nowemu?
  9. Co żegnamy w PHP 7?

Zacznę od szybkiego sprostowania. Nie, nie przegapiliście wersji nr 6 :). Społeczność dokonała przeskoku, głównie z tego powodu, że kluczowe zmiany, które miała przynieść „szóstka” zostały wydane jeszcze w „piątce”. Dlatego, żeby zachować road map, twórcy zdecydowali się na przeskoczenie o jedno oczko.

Jak wzrosła szybkość PHP 7?

Czas ładowania witryny i obciążenie serwerów to jedne z najważniejszych czynników, na które zwraca się uwagę w projektach internetowych. Nic dziwnego, w końcu szybsze działanie przekłada się na komfort użytkowania, konwersję, a także potrafi znacząco obniżyć koszt infrastruktury.

Dlatego twórcy PHP włożyli dużo wysiłku w przepisanie języka na tyle, żeby mówić o wyraźnym skoku wydajności. Czy udało im sie? Sprawdźcie poniższe źródła:

Widać bardzo wyraźnie jakim sukcesem zakończyła się optymalizacja PHP 7. W niektórych przypadkach skok wydajnościowy w stosunku do PHP 5.6 jest dwukrotny. Co więcej, testy odbywały się na „prawdziwych” rozwiązaniach, takich jak Magento, WordPress czy Drupal. Biorąc pod uwagę, jaki w sumie te rozwiązania mają udział rynkowy, to zdecydowanie dobra wiadomość dla bardzo dużej rzeszy użytkowników.

Wydajność PHP 7 e-commerce
Tak prezentuje się wydajność PHP na przykładzie Magento

Jaki jeszcze jest zysk z przyspieszenia?

Przede wszystkim wspomniany koszt mocy obliczeniowej potrzebny do obsługi ruchu w aplikacji. Zwróciłem uwagę, że w niektórych naszych projektach, aktualizacja serwerów do PHP 7 (co nie zawsze jest takie łatwe, szczególnie ze względu na zakończenie wsparcia dla niektórych składni), serwery po prostu zaczynały się nudzić. Dzięki temu albo można obniżyć ich moc, albo spokojnie przesunąć moment, w którym skala ruchu wymusi zwiększenie zasobów. (Jeszcze mala dygresja – zwóćcie uwagę, że wraz z wyższą wersją HHVM spada przewaga PHP 7. Warto to wziąć pod uwagę w swoich projektach).

Jeśli chcecie jak sami sprawdzić jak sprawuje się wasz serwer, możecie sprawdzić to za pomocą narzędzia https://github.com/facebookarchive/oss-performance

Jakie nowości w PHP 7?

Poza znaczącym przyspieszeniem, twórcy postawili na zmiany w samym języku. W mojej ocenie, idzie dobre. Z resztą, sami zobaczcie.

Wskazanie typów skalarnych & zwrócenie wartości o określonym typie

Od wersji PHP 5 możemy wskazać jakiego obiektu oczekujemy w parametrze:

<?php
function method1(DateTime $myDateTimeObj)
{
    // do sth
}

W PHP 7 otrzymujemy możliwość wymuszenia zwrócenia wartości określonego typu. Nie ukrywam, z punktu widzenia higieny kodu, mogę śmiało powiedzieć wreszcie Tak wygląda to na przykładzie:

<?php
function add(int $a, int $b) : int
{
  return $a + $b;
}

var_dump(add(1000, 1)); // int(1001)
var_dump(add('1000', '1')); // int(1001)

Jeśli przyjrzycie się uważnie, można pomyśleć „Ale zaraz! Przecież w drugim wywałoaniu pojawiają się napisy! Oszukali nas?!”. Z czego to wynika? Otóż PHP dość liberalnie podchodzi do sprawdzania typu 10, 10e2, '100', '100meEither' (wszystkie zostaną potraktowane jako int (ostatni przypadek wyrzuci przy okazji ostrzeżenie)).

Jeśli mimo wszystko chcecie, żeby PHP był bardziej „gorliwy” w sprzawdzaniu typów, trzeba posłużyć się taką oto dyrektywą:

<?php
declare(strict_types=1);

Co ważne, musi być ona dołączona w pierwszej linii i działa tylko w obrębie wykonywanego pliku. Co to znaczy?

// my_mul.php
<?php
declare(strict_types=1);

function multiply(int $a, int $b) : int
{
  return $a * $b;
}

// use_mul.php
<?php

include('my_mul.php');
var_dump(multiply('10', 10)) // int(100)

ale

// use_mul_v2.php
<?php
declare(strict_types=1);

include('my_mul.php');
var_dump(multiply('10', 10)) // Uncaught TypeError: Argument 1 passed to multiply() must be of the type integer, string given

Więcej informacji: https://wiki.php.net/rfc/scalar_type_hints_v5 (RFC)

Czym jest “Kosmiczny” operator <=>?

Kosmiczny operator (orginalnie spaceship operator) jest kombinacją wszystkich trzech użytych w jego konstrukcji i w zależności od tego, która jest prawdziwa, zwraca inną wartość. To rozwiązanie, które zdecydowanie zwiększa czytelność kodu, a poza tym – kto nie chciałby używać operatora, któremu nadano taką nazwę :).

Jak spaceship operator działa w praktyce?

echo 1 <=> 1; // 0 - dla równych wartości
echo 1 <=> 0; // 1 - jeśli lewa wartość jest większa od wartości po prawej
echo 0 <=> 1; // -1 - jeśli lewa wartość jest mniejsza od wartości po prawej

taka konstrukcja działa także dla napisów

echo "x" <=> "x"; // 0
echo "y" <=> "x"; // 1
echo "y" <=> "z"; // -1

jak również tablic i obiektów:

echo [] <=> []; // 0
echo [1, 2, 3] <=> [1, 2, 3]; // 0
echo [1, 2, 3] <=> [1, 2]; // 1
echo [1, 2, 4] <=> [1, 2, 3]; // 1
echo [1, 2] <=> [1, 2, 3]; // -1
echo [1, 2, 3] <=> [1, 2, 4]; // -1

$a = (object) ["field" => "abc"]; 
$b = (object) ["field" => "abc"]; 
echo $a <=> $b; // 0

$a = (object) ["other_field" => "cba"]; 
$b = (object) ["other_field" => "abc"]; 
echo $a <=> $b; // 1


$a = (object) ["one_another" => "abc"]; 
$b = (object) ["one_another" => "cba"]; 
echo $a <=> $b; // -1

Więcej informacji: https://wiki.php.net/rfc/combined-comparison-operator

Czym jest Null Coalesce Operator (??)

To kolejny operator, który zwiększa nam czytelność kodu i powoduje, że ten jest nieco krótszy. Tę konstrukcję stosuje się non stop, więc osobiście jestem bardzo zadowolony, że ktoś pomyślał o tego typu rozwiązaniu. W skrócie – operator sprawdza, czy wartość jest NULL i zwraca lewą stronę operatora, jeśli nie i prawą, jeśli tak. Przykład:

Proste zastosowanie

$username = $user->getName() ?? 'nobody';
$width = $imageData['width'] ?? 100;

Odpowiednik

$username = ($user->getName() === NULL) ? 'nobody' : $user->getName()
$width = ($imageData['width'] === NULL) ? '100' : $imageData['width']

onstrukcja jest dość intuicyjna, oszczędza czas na pisaniu i lepiej się ją czyta.

Bardziej złożony przykład

Po kolei próbujemy pozyskać konfigurację ze zmiennej $_GET['config'] jeśli nie ma żadnej wartości próbujemy pozyskać informację z pola $this->config. Jeśli i tu nie znajdziemy żadnej wartości wykorzystujemy wartość static::$defaultConfig

$defaultConfig = $_GET['config'] ?? $this->config ?? static::$defaultConfig;

Zostawiam jako zadanie domowe napisanie tego „po staremu”.

Czym są klasy anonimowe?

Gdy nie zawsze chcemy (chce -opłaca się nam) tworzyć niezwykle mało użyteczną klasę możemy posłużyć się klasą anonimową. Przykład z Symfony wzięty:

use Symfony\Component\Process\Process;

$process = new class extends Process {
  public function start()
{
            // do sth
  }
};

zamiast

namespace My\Namespace\Process;

use Symfony\Component\Process\Process as Base;

class Process extends Base {
  public function start() {
            // do sth
  }
}

$process = new \My\Namespace\Process\Process;

Czy to na pewno jest ładnie? A to nie jest!?

array_walk($arr, function($v, $k) {
  echo 'Key: '.$k.' value: '.$k;
});

Więcej informacji: https://wiki.php.net/rfc/anonymous_classes

Czym jest stała tablicowa via define()?

Od wersji 5.6 tablice mogą być także stałymi:

<?php
const MY_ARRAY = [
  'hey',
  'it's',
  'an',
  'array!',
];

ale dopiero wersja 7 wprowadza możliwość definiowania takiej stałej przy pomocy funkcji define()

<?php
define('MY_ARRAY', [
  'I\'m',
  'an array',
  'too!',
]);

Jak zmienia się grupowanie deklaracji?

do PHP 5.6

<?php
use Framework\Component\ClassA;
use Framework\Component\ClassB as ClassC;
use Framework\Component\OtherComponent\ClassD

od PHP 7

<?php
use Framework\Component\{
    ClassA,
    ClassB as ClassC,
    OtherComponent\ClassD
};

można używać też do funkcji i stałych

<?php
use Framework\Component\{
    ClassA,
    function OtherComponent\someFunction,
    const OtherComponent\SOME_CONSTANT
};

Łap błędy!

W PHP 5.x błędy [RECOVERABLE] FATAL ERROR powodowały zatrzymanie skryptu, a dlaczego by ich nie złapać? Od teraz można. Nowa hierarchii wyjątków:

interface Throwable
  |- Exception implements Throwable
        |- ...
  |- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- ArithmeticError extends Error
            |- DivisionByZeroError extends ArithmeticError
        |- AssertionError extends Error

<?php
$foo = new class() {
  public function bar()
  {
            echo 'works';
  }
};

try {
  $foo->bar(); // works
  $foo->bar2(); // bar2 caused an error!
} catch (Error $e) {
  echo 'bar2 caused an error!';
}

Jak wyglądają wyrażenia po nowemu?

Szybkość nowej wersji wzięła się m. in. z wprowadzenia AST, co wpłynęło również na sposób interpretowania zapisanych wyrażeń:

WyrażenieDziałanie w PHP 5Działanie w PHP 7
$$foo[‚bar’][‚baz’]${$foo[‚bar’][‚baz’]}($$foo)[‚bar’][‚baz’]
$foo->$bar[‚baz’]$foo->{$bar[‚baz’]}($foo->$bar)[‚baz’]
$foo->$bar[‚baz’]()$foo->{$bar[‚baz’]}()($foo->$bar)[‚baz’]()
Foo::$bar[‚baz’]()Foo::{$bar[‚baz’]}()(Foo::$bar)[‚baz’]()

Więcej informacji: https://wiki.php.net/rfc/abstract_syntax_tree

Co żegnamy w PHP 7?

Wraz z nową wersją, będziemy musieli pożegnać się ze starymi nawykami. to główny powód, dla którego często aktualizacja serwera nie musi przebiegać wcale tak dobrze, jak mogłoby się wydawać. Może się bowiem okazać, że będzie trzeba poprawić stare skrypty, które nie trzymają się nowych standardów.

  • <% // php code %>
  • <script language='php'> // php code </script>
  • Wszystkie funkcje ereg_ (zdeprecjonowane już od wersji 5.3.0); zastąpione przez funkcje preg_
  • Wszystkie funkcnie mysql_ (zdeprecjonowane w wersji 5.5.0); zastąpione prze funkcje mysqli_ lub PDO
  • Funkcja split() (zdeprecjonowana od wersji 5.3.0); możliwe alternatywy preg_split()explode()str_split()
  • Wielokrotne użycie domyślnego wariantu (default) w wyrażeniu switch

Statyczne odwołania do niestatycznych metod będą usunięte w przyszłości (co jest komunikowane odpowiednim ostrzeżeniem)

class foo()
{
  public function bar()
  {
        echo 'regular method';
  }
}

foo::bar(); // Deprecated: Non-static method foo::bar() should not be called statically
            // regular method

Konstruktory znane z PHP 4 również są oznaczone jako przestarzałe

class foo2()
{
  public function foo2()
  {
        echo 'the constructor';
  }
} // Deprecated:  Methods with the same name as their class will not be constructors in a future version of PHP;

Więcej informacji: https://php.net/manual/en/migration70.deprecated.php

To oczywiście nie wszystkie zmiany (starałem się wybrać raczej te najciekawsze i te które moim zdaniem są przydatne). Jeśli jesteście ciekawi wszystkich zmian w stosunku do wersji 5.6 zajrzyjcie na https://php.net/manual/en/migration70.php