Novinky jazyka PHP 7

Ján Bodnár 21. 3. 2016

V tomto článku si opíšeme novinky jazyka PHP 7. Zameriame sa na jadro jazyka PHP. Príklady budeme písať pre PHP CLI, čo je verzia jazyka určená pre príkazový riadok.

Medzi rokmi 2014 a 2015 bola vyvinutá nová hlavná verzia jazyka PHP, ktorá bola označená za PHP 7. Posledným dôležitým míľnikom bola verzia PHP 5.6. Verzia PHP 6 bola vypustená kvôli neúspechu unikódovej vetvy jazyka, ktorá nebola nakoniec dotiahnutá do konca. Keďže viacero zdrojov odkazovalo na túto vetvu ako na budúce PHP 6, vývojári sa rozhodli zvoliť radšej verziu PHP 7.

PHP

PHP je populárny skriptovací jazyk, ktorý sa používa na vývoj dynamických webových stránok. Odhaduje sa, že až 80 % dynamických stránok používa jazyk PHP. Domovskou stránkou PHP je php.net, ktorá obsahuje rozsiahlu dokumentáciu k jazyku. Na stránke zetcode.com/lang/php/ nájdete podrobný PHP tutoriál v rozhraní PHP CLI.

Inštalácia PHP

Zo stránky php.net/downloads.php si stiahneme zdrojové súbory najnovšej verzie PHP 7; my si stiahneme súbor php-7.0.4.tar.bz2.

$ bunzip2 php-7.0.4.tar.bz2
$ tar -xf php-7.0.4.tar
$ cd php-7.0.4/

Dekomprimujeme súbor a premiestnime sa do adresára php-7.0.4/.

$ sudo apt-get install libreadline-dev
$ sudo apt-get install libicu-dev

Potrebujeme doinštalovať tieto dva balíčky; prvý je určený na vybudovanie interaktívneho shellu, druhý je nevyhnutný na kompiláciu medzinárodných unikódových komponentov.

$ ./configure --with-readline --enable-intl

Spustíme script configure s podporou modulov readline a intl.

$ make
$ sudo make install

Skompilujeme a nainštalujeme PHP. Je možné, že bude treba doinštalovať ďalšie balíčky; napríklad libxml2-dev.

$ php -a
Interactive shell

php > echo PHP_VERSION;
7.0.4

Po úspešnej inštalácii si spustíme interaktívny PHP shell príkazom php -a. Konštanta PHP_VERSION obsahuje verziu jazyka PHP.

Celočíselné delenie

Operátor delenia (/) vracia desatinné číslo. Nová funkcia intdiv() vykonáva celočíselné delenie. Jej prvým parametrom je delenec, druhým je deliteľ.

<?php

    echo 7/3, "\n";
    echo intdiv(7, 3), "\n";

?>

Tento príklad vypíše hodnoty desatinného a celočíselného delenia čísiel 7 a 3.

$ php integer_division.php
2.3333333333333
2

Toto je výstup programu integer_division.php.

Trojcestný operátor <=>

Nový trojcestný operátor (<=>), nazývaný aj kombinovaný porovnávací operátor, zjednodušuje písanie zreťazených porovnávacích úkonov. Operátor sa správa podobne ako funkcie strcmp() alebo version_compare().

$a <=> $b

Tento výraz vracia –1 ak je $a menšie ako $b, 0 ak je $a rovné $b, a 1 ak je $a väčšie ako $b.

($a < $b) ? -1 : (($a > $b) ? 1 : 0)

Vyššie uvedený výraz je napísaný bez použitia trojcestného operátoru.

<?php

    echo 1 <=> 1; // 0
    echo 1 <=> 2; // -1
    echo 2 <=> 1; // 1

    echo "\n";

    echo "a" <=> "a"; // 0
    echo "a" <=> "b"; // -1
    echo "b" <=> "a"; // 1

    echo "\n";

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

    echo "\n";

?>

V príklade použijeme kombinovaný porovnávací operátor na čísla, reťazce, a polia.

echo 1 <=> 1; // 0

Čísla sú porovnávané podľa veľkosti.

echo "a" <=> "b"; // -1

Reťazce sú porovnávané lexikálne, to znamená podľa ich abecedného poradia.

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

V prípade polí sú porovnávané zodpovedajúce elementy poľa.

$ php spaceship_operator.php
0-11
0-11
0011-1

Toto je výstup skriptu spaceship_operator.php.

Operátor ??

Operátor (??) umožňuje zjednodušiť kód v prípadoch, keď sa využíva ternárny operátor spolu s funkciou isset(), čo je v PHP častá záležitosť. Operátor vracia svoj prvý operand ak existuje a nemá hodnotu null; v opačnom prípade vracia druhý operand.

php > $mycolour = $colour ?? "blue";
php > echo $mycolour;
blue

Keďže premenná $colour nie je definovaná, operátor ?? vracia druhý operand.

php > $mycolour = isset($colour) ? $colour : "blue";
php > echo $mycolour;
blue

V predchádzajúcich verziách PHP sa využívala funkcia isset(), ktorá skúma existenciu premennej a zároveň či premenná nie je rovná null.

Deklarácie skalárnych typov

Deklarácie skalárnych typov umožňujú typovú kontrolu parametrov funkcií. Vynútené môžu byť tieto dátové typy: string, int, float, a bool.

Kontrola dátových typov nie je aktívna automaticky, je potrebné ju zapnúť funkciou declare().

<?php

// declare(strict_types=1);

function add(int $a, int $b) {

    return $a + $b;
}

$r = add(4, 6);
echo "$r\n";

$r = add("4", "6");
echo "$r\n";

?>

Vo východzom režime vypíše skript 10 v oboch prípadoch. Ak zapneme striktný mód, skript sa ukončí chybovou hláškou: Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type integer, string given.

Deklarácie návratových typov

Deklarácie návratových typov uvádzajú typ hodnoty, ktorá sa vracia funkciou. Vynútiť sa môžu rovnaké dátové typy, ako v prípade typových deklarácií argumentov.

<?php

declare(strict_types=1);

function add( $a, $b) : int {

    return $a + $b;
}

$r = add(4, 6.0);
echo "$r\n";

?>

Funkcia add() deklaruje návratový typ ako int. Funkcia však vracia desatinné číslo; preto sa skript končí chybovou hláškou: Fatal error: Uncaught TypeError: Return value of add() must be of the type integer, float returned.

Escape sekvencie unikódových kódových bodov

Nová syntax umožňuje písanie unikódových znakov pomocou kódových bodov v hexadecimálnej forme. (Kódové body sú numerické hodnoty, ktoré reprezentujú ľubovoľný znak, prípadne formátovanie znakov.)

<?php

    echo "\u{13C7}", "\n";
    echo "\u{1307}", "\n";

?>

Príklad vypíše čerokézske písmeno que a etiópsku slabiku jwa.

$ php unicode_codepoints.php
Ꮗ
ጇ

Toto je výstup skriptu unicode_codepoints.php.

IntlChar trieda

Nová trieda IntlChar modulu intl ponúka viacero utilít pre získavanie informácií o unikódových znakoch.

<?php

    echo IntlChar::charName('Ꮗ'), "\n";
    echo IntlChar::charName('ጇ'), "\n";

?>

V príklade využívame funkciu IntlChar::charName() na získanie mena uvedeného znaku.

$ php intlcharex.php
CHEROKEE LETTER QUE
ETHIOPIC SYLLABLE JWA

Toto je výstup skriptu intlcharex.php.

Anonymné triedy

Anonymné triedy sú triedy, ktorým nebol pridelený názov. Sú praktické ak potrebujeme jednoduché, jednoúčelové objekty.

<?php

    $c = new class {

        public function say() {
            echo "This is an anonymous class\n";
        }
    };

    $c->say();

?>

V príklade priradíme objekt premennej $c. Objekt bol vytvorený z anonymnej triedy.

$ php anonymous_class.php
This is an anonymous class

Toto je výstup skriptu anonymous_class.php.

Očakávania

Očakávania, v angličtine expectations, sú rozšírením staršej funkcie assert(), s ktorou sú spätne kompatibilné. (Anglické slová expect a assert majú podobný význam.) Očakávania umožňujú vytvárať výroky s nulovými nákladmi v produkčnom nasadení a vyvolať výnimky, ak tieto výroky zlyhajú.

Konfiguračné nastavenie zend.assertions môže mať jednu z týchto troch hodnôt:

  • 1 — vytvoriť a spustiť kód (vývojový mód)
  • 0 — vytvoriť kód a preskočiť ho počas behu programu
  • –1 — nevytvoriť žiadny kód (nulové náklady, produkčný mód)

Nastavenie assert.exception určuje, či sa vyvolá výnimka ak výrok zlyhá. Toto nastavenie je normálne vypnuté kvôli spätnej kompatibilite s funkciou assert().

<?php

abstract class Grades {

    const A = 0;
    const B = 1;
    const C = 2;
    const D = 3;
    const E = 4;
    const FX = 5;
}

const G = 6;

//$grade = Grades::A;
$grade = G;

switch ($grade) {

    case Grades::A:
        echo "Outstanding\n";
    break;

    case Grades::B:
        echo "Superior\n";
    break;

    case Grades::C:
        echo "Good\n";
    break;

    case Grades::D:
        echo "Satisfactory\n";
    break;

    case Grades::E:
        echo "Low pass\n";
    break;

    case Grades::FX:
        echo "Failure\n";
    break;

    default:
        assert(false, "Unrecognized grade passed through switch: {$grade}");
}

echo "code continues\n";

?>

V príklade sme vytvorili systém vysokoškolských známok.

//$grade = Grades::A;
$grade = G;

Nesprávna hodnota je zadaná premennej $grade.

default:
    assert(false, "Unrecognized grade passed through switch: {$grade}");

Pridáme výrok do miesta, ku ktorému by sa program nemal dopracovať.

$ php expectations.php

Warning: assert(): Unrecognized grade passed through switch: 6
failed in /home/janbodnar/prog/php7/expectations.php on line 44
code continues

Ak spustíme skript, dostaneme uvedené varovanie. Skript však pokračuje.

ini_set('assert.exception', 1);

Ak aktivujeme výnimky pomocou ini_set(), skript sa ukončí s chybovou hláškou: Fatal error: Uncaught AssertionError: Unrecognized suit passed through switch: 6.

Parameter levels funkcie dirname()

Funkcia dirname() vracia cestu rodičovského adresára. Parameter levels stanovuje, koľko rodičovských adresárov máme preskočiť.

dirlevels.php
<?php

    echo dirname('/usr/local/bin').PHP_EOL;
    echo dirname('/usr/local/bin', 1).PHP_EOL;
    echo dirname('/usr/local/bin', 2).PHP_EOL;
    echo dirname('/usr/local/bin', 3).PHP_EOL;

?>

V tomto skripte si vyskúšame viaceré volania funkcie dirname() s rôzne nastaveným parametrom levels .

$ php dirlevels.php
/usr/local
/usr/local
/usr
/

Tu máme výstup skriptu dirlevels.php.

Konštantné polia pomocou define()

Od verzie PHP 5.6 je možné definovať konštantné polia pomocou kľúčového slova const. V PHP 7, konštantné pole je možné definovať aj pomocou funkcie define().

<?php

    const POSSIBLE_GRADES = ['A', 'B', 'C', 'D', 'E', 'FX'];
    define('ALLOWED_IMAGE_EXTENSIONS', ['jpg', 'jpeg', 'gif', 'png']);

    print_r(POSSIBLE_GRADES);
    print_r(ALLOWED_IMAGE_EXTENSIONS);

?>

V príklade definujeme dve konštantné polia; jedno pomocou const, druhé pomocou define().

$ php array_constants.php
Array
(
    [0] => A
    [1] => B
    [2] => C
    [3] => D
    [4] => E
    [5] => FX
)
Array
(
    [0] => jpg
    [1] => jpeg
    [2] => gif
    [3] => png
)

Toto je výstup skriptu array_constants.php.

Nová syntax kľúčového slova use

Pomocou kľúčového slova use môžme použiť viaceré deklarácie v jednej inštrukcii.

<?php

    namespace ZetCode;

    class Night {

        function say() {

            echo "This is Night class\n";
        }
    }

    class Day {

        function say() {

            echo "This is Day class\n";
        }
    }

    function f1() {

        echo "This is f1()\n";
    }

    function f2() {

        echo "This is f2()\n";
    }

?>

V súbore myfile.php máme dve triedy a dve funkcie vnorené v mennom priestore ZetCode.

<?php

    include 'myfile.php';

    use ZetCode\{Day, Night};
    use function ZetCode\{f1, f2};

    $d = new Day();
    $d->say();

    $n = new Night();
    $n->say();

    f1();
    f2();

?>

V súbore usegroup.php použijeme triedy a funkcie z menného priestoru ZetCode namespace.

use ZetCode\{Day, Night};
use function ZetCode\{f1, f2};

Pomocou novej syntaxe kľúčového slova use importujeme viaceré deklarácie v jednej inštrukcii.

use ZetCode\Day;
use ZetCode\Night;
use function ZetCode\f1;
use function ZetCode\f2;

V predchádzajúcich verziách jazyka PHP bolo treba importovať triedy a funkcie po jednom.

$ php usegroup.php
This is Day class
This is Night class
This is f1()
This is f2()

Toto je výstup skriptu usegroup.php.

Metóda Closure::call

V PHP 5.4 pribudla možnosť naviazať lexikálne uzávery (closures) do oblasti objektu. Tento proces je v PHP 7 zjednodušený pomocou metódy call().

<?php

    class Greeting {

        private $word = "Hello";
    }

    $f = function($whom) {

        echo "$this->word $whom\n";
    };

    $obj = new Greeting();

    $f->call($obj, 'Tom');
    $f->call($obj, 'Jane');

?>

Pomocou closure získame prístup k privátnej premennej $word triedy Greeting.

$ php closure_call.php
Hello Tom
Hello Jane

Toto je výstup skriptu closure_call.php.

Výraz return v generátoroch

V PHP 7 môžme použiť kľúčové slovo return pre návrat finálneho výrazu v generátore. Táto hodnota sa môže vytiahnuť pomocou metódy getReturn(), ktorá sa môže volať až keď generátor ukončí posielanie hodnôt.

<?php

    srand();

    function random_numbers($k) {

        for ($i=0; $i<$k; $i++) {

            $r = rand(1, 10);

            yield $r;
        }

        return -1;
    }

    $rns = random_numbers(10);

    foreach ($rns as $r) {

        echo "$r\n";
    }

    echo $rns->getReturn() . PHP_EOL;

?>

Náš generátor vytvára náhodné čísla. Na konci iterácií, generátor vráti hodnotu –1.

$ php generator_return.php
7
5
6
7
1
1
9
6
5
8
-1

Toto je výstup skriptu generator_return.php.

Delegácia generátoru pomocou yield from

Delegácia generátorov umožňuje poskytovať hodnoty z iných generátorov, Traversable objektov, alebo polí pomocou yield from. To vedie k čistejšiemu kódu a lepšej použiteľnosti kódu.

<?php

    function f1() {

        yield from f2();

        yield "f1() 1";
        yield "f1() 2";

        yield from [3, 4];

        yield "f1() 3";
        yield "f1() end";
    }

    function f2() {

        yield "f2() 1";
        yield "f2() 2";
        yield "f2() 3";
        yield "f2() end";
    }

    $f = f1();

    foreach ($f as $val) {
        echo "$val\n";
    }

?>

V príklade použijem hodnoty z poľa a iného generátoru.

function f1() {

    yield from f2();
...

Tu použijeme hodnoty z generátoru f2().

yield from [3, 4];

Tu zas z dvojprvkového poľa.

$ php generator_delegation.php
f2() 1
f2() 2
f2() 3
f2() end
f1() 1
f1() 2
3
4
f1() 3
f1() end

Toto je výstup programu generator_delegation.php.

Uniformovaná syntax premenných

Nová uniformovaná syntax premenných umožňuje také kombinovanie operátorov, ktoré v minulých verziách neboli možné. Nová syntax vždy vyhodnocuje premenné zľava doprava. V niektorých prípadoch dochádza k porušeniu spätnej kompatibility výrazov, kde sa využíva stará evaluácia.

<?php

    function e() {

        echo "This is e() \n";
    };

    function f() {

        echo "This is f() \n";
        return e;
    };

    function g() {

        echo "This is g()\n";
        return f;
    };

    g();
    echo "***********\n";
    g()();
    echo "***********\n";
    g()()();

?>

V príklade použijeme g()() a g()()(), ktoré neboli možné v predchádzajúcich verziách PHP.

g();

Tu spustíme funkciu g().

g()();

V tomto prípade sa spustí funkcia g() a funkcia vrátená z funkcie g().

g()()();

Je vykonaná funkcia g(), funkcia vrátená z funkcie g(), a funkcia vrátená z vrátenej funkcie g().

$ php uniform_variable_syntax.php
This is g()
***********
This is g()
This is f()
***********
This is g()
This is f()
This is e()

Toto je výstup skriptu uniform_variable_syntax.php.

Ďalej si ukážeme ďalší príklad syntaxe, ktorá je možná vďaka novej, uniformovanej syntaxe premenných.

<?php

    class A {

        function f() {

            echo "f() of A\n";
        }
    }

    class B {

        function g() {
            echo "g() of B\n";

            return new A();
        }
    }

    $b = new B();
    $b->g()::f();

?>

Výraz $b->g()::f() vykoná metódu f() triedy A, navrátenej metódou g() triedy B.

$ php uniform_variable_syntax2.php
g() of B
f() of A

Toto je výstup skriptu uniform_variable_syntax2.php.

Nakoniec si ukážeme príklad okamžite vyvolaného funkčného výrazu, ktorý je známy z jazyka JavaScript.

<?php

    $v = (function() {

        return 11 - 5;
    })();

    echo "$v\n";

?>

Ide o anonymnú funkciu, ktorá je vykonaná hneď potom, čo bola definovaná.

$ php uniform_variable_syntax3.php
6

Toto je výstup programu uniform_variable_syntax3.php.

Zmeny vo výnimkách a chybách

V PHP 7 je volaná výnimka pri fatálnych a obnoviteľných chybách ( E_ERROR a E_RECOVERABLE_ERROR). V predchádzajúcich verziách PHP došlo ku zastaveniu skriptu. Tieto chyby sú inštanciou novej, separátnej triedy Error. Niektoré chyby hádžu konkrétnejšie druhy výnimiek: TypeError, ParseError, ArithmeticError, a AssertionError

Varovania (warnings) a upozornenia (notices) zostávajú nezmenené; iba fatálne chyby a obnoviteľné chyby vyvolávajú zatiaľ výnimky.

Rozhranie Throwable bolo vytvorené na zjednotenie dvoch rôznych vetiev výnimiek: Exception a Error; v PHP 7 obe triedy implementujú Throwable rozhranie.

<?php

    class A {

        public function say() {

            echo "This is class A\n";
        }
    }

    try {

        $a = new A();
        $a->say();

        $a = null;
        $a->say();

    } catch (Error $e) {

        echo "Error occured" . PHP_EOL;
        echo $e->getMessage() . PHP_EOL ;
        echo "File: " . $e->getFile() . PHP_EOL;
        echo "Line: " . $e->getLine(). PHP_EOL;
    }

    echo "Script continues\n";

?>

V tomto príklade voláme metódy na nulovom objekte. Zachytíme výnimku a spracujeme ju. Skript ďalej pokračuje.

$ php null_error.php
This is class A
Error occured
Call to a member function say() on null
File: /home/janbodnar/prog/php7/null_error.php
Line: 17

Toto je výstup skriptu null_error.php.

DivisionByZeroError pochádza z ArithmeticError; je volaná v prípade ak pri funkcii intdiv() a module operátore (%) je deliteľ 0. Operátor delenia () spôsobí naďalej varovanie Warning: Division by zero. Tieto nezrovnalosti sú spôsobené návrhovými chybami v minulosti, ktoré sa postupne naprávajú.

<?php

    $a = 4;
    $b = 0;

    try {

        $c = intdiv($a , $b);

    } catch (DivisionByZeroError $e) {

        echo "Error occured\n";
        echo $e->getMessage() . PHP_EOL ;
        echo "File: " . $e->getFile() . PHP_EOL;
        echo "Line: " . $e->getLine(). PHP_EOL;
    }

    echo "$c \n";

    echo "Script continues\n";

?>

V príklade zachytíme a reportujeme výnimku DivisionByZeroError volanú z funkcie intdiv().

$ php arithmetic_error.php
Error occured
Division by zero
File: /home/janbodnar/prog/php7/arithmetic_error.php
Line: 8

Script continues

Toto je výstup skriptu arithmetic_error.php.

Zdroje

Nasledovné zdroje boli použité pri písaní tohto článku:

V tomto článku sme prezentovali novinky jazyka PHP 7. Článok vyšiel v angličtine na autorovej webovej stránke.

Našli jste v článku chybu?
DigiZone.cz: Brexit na ČT 24? Skoro 2 miliony...

Brexit na ČT 24? Skoro 2 miliony...

Podnikatel.cz: Český podnikatel a #brexit. Co nastane?

Český podnikatel a #brexit. Co nastane?

120na80.cz: Cestovní nevolnost. Co pomůže?

Cestovní nevolnost. Co pomůže?

DigiZone.cz: Prima Max bude mít letní kino. Na střeše...

Prima Max bude mít letní kino. Na střeše...

Vitalia.cz: 3× o tucích: proč je potřebujeme?

3× o tucích: proč je potřebujeme?

Podnikatel.cz: Oblíbené Babišovo reverse charge. Potopilo je?

Oblíbené Babišovo reverse charge. Potopilo je?

120na80.cz: Proč komáři létají hlavně večer?

Proč komáři létají hlavně večer?

Měšec.cz: Od kdy musí studenti platit pojistné?

Od kdy musí studenti platit pojistné?

Lupa.cz: Text umírá, na webu zbude jen video

Text umírá, na webu zbude jen video

Měšec.cz: Apple Pay je v Česku. Návod na aktivaci

Apple Pay je v Česku. Návod na aktivaci

Měšec.cz: Co s reklamací, když e-shop krachuje?

Co s reklamací, když e-shop krachuje?

DigiZone.cz: Markíza: tady je předběžné opatření

Markíza: tady je předběžné opatření

Vitalia.cz: Jelení farma produkuje kvalitní maso

Jelení farma produkuje kvalitní maso

Lupa.cz: Nej aplikace? Vodafone, Mozkovna, Záchranka

Nej aplikace? Vodafone, Mozkovna, Záchranka

Měšec.cz: TEST: Vyzkoušeli jsme pražské taxikáře

TEST: Vyzkoušeli jsme pražské taxikáře

Vitalia.cz: Epidemie: Klíšťová encefalitida po ovčím sýru

Epidemie: Klíšťová encefalitida po ovčím sýru

DigiZone.cz: Nova: technické pauzy každé 1. pondělí

Nova: technické pauzy každé 1. pondělí

Měšec.cz: Ceny PHM v Evropě. Finty na úspory

Ceny PHM v Evropě. Finty na úspory

Vitalia.cz: 7 receptur z pohanky. Svědčí zdraví

7 receptur z pohanky. Svědčí zdraví

DigiZone.cz: Noxon iRadio 1 W bude za pár měsíců

Noxon iRadio 1 W bude za pár měsíců