Що там у PHP: поточний стан мови і майбутні зміни

  • 16 марта, 16:07
  • 1855
  • 0

Не секрет, що PHP має не найкращу репутацію серед розробників. Незважаючи на те, що PHP як і раніше є одною з найбільш часто використовуваних мов для створення інтернету, за роки існування вона зуміла завоювати собі репутацію мови з жахливою організацією несекьюрного повного дірок коду, недосвідченими розробниками, нестабільними бібліотеками і т.д.

Однак багато з цього уже в минулому. Це гідна мова, на якій ви можете писати сучасні, керовані і надійні додатки.

Що там у PHP: поточний стан мови і майбутні зміни

Як все це було

Перш ніж заглиблюватися в деталі, розглянемо, як PHP розвивається в наші дні. Зараз ми знаходимося на версії 7.4, а наступною версією, яка з'явиться в кінці 2020 року, буде PHP 8.

В кінці ери 5. *, розробники намагалися зберегти послідовний річний цикл випуску апдейтів, і успішно справлялися з цим протягом останніх чотирьох років. Кожен новий реліз активно підтримувався два роки, і отримував ще один рік "security fixes only".

Розробкою PHP в наші дні займається група волонтерів, а деякі з них навіть отримують плату за повний робочий день. 

Фреймворки і екосистема

Існує два основних фреймворка для інтернету і кілька дрібніших: Symfony і Laravel. Звичайно, є і Laminas, Yii, Cake, Code Igniter і т.д. - але якщо ви хочете знати, як виглядає сучасна розробка на PHP, то беріть один з перших двох.

Обидві платформи мають велику екосистему пакетів і готових продуктів. Починаючи від адмінок, CRM, автономних пакетів, до CI і профілів, численних сервісів: сервери веб-сокетів, менеджери черг, платіжні інтеграції і маса інших корисних речей.

Дані фреймворки призначені для розробки. Якщо завдання тільки в чистому управлінні контентом, вибирайте WordPress, CraftCMS і Statamic т.д. Вони постійно удосконалюються.

Один із способів виміряти поточний стан екосистеми PHP - це подивитися на основний репозиторій для PHP. Близько 25 млн завантажень в день. А ось графік з кількістю пакетів і версій з плином часу:

Ще більше PHP-статистики ви знайдете на Packagist .

Крім фреймворків і CMS, в останні роки спостерігається зростання асинхронних фреймворків ( Swool, Amp і ReactPHP). Цей софт написаний на PHP або іншими мовами, що дозволяють користувачам запускати асинхронний код.

Продуктивність

За часів п'ятої версії продуктивність PHP була в кращому випадку середня. Починаючи з версії 7.0, велика частина ядра була переписана, що призвело до збільшення продуктивності в 2-3 рази. Кожен новий реліз робить позитивний вплив на продуктивність, і це можна спостерігати на бенчмарках.

Ще одна фіча, що підвищує продуктивність - прелоадінг, що дозволяє зберігати скомпільовані частини коду в пам'яті. 

Коли з'явиться PHP 8, в список пришвидшувачів додасться JIT-компілятор, який також дозволить PHP увійти в нові області крім веб-розробки.

Система типів

З тих пір як PHP почали використовувати для великих проектів, стали очевидні недоліки типізації, виникла необхідність в більш сильній підтримці типів. Сьогодні PHP як і раніше дозволяє писати повністю динамічний і слабо типізований код, але має більш якісну систему типів. У поєднанні з інструментами аналізу, такими як Psalm, Phan і PHPStan, можна написати безпечний і строго типізований код.

Візьмемо, наприклад, цей фрагмент коду PHP, який використовує сучасну систему типів:

<?php

declare(strict_types=1);

final class Foo
{
    public int $intProperty = 2;
    public ?string $nullableString = null;
    private Bar $bar;
    public function __construct(Bar $bar) {
        $this->bar = $bar;
    }

    public function withInt(int $value): self
    {
        $clone = clone $this;
        $clone->intProperty = $value;
        return $clone;
    }
  
    public function unionTypes(int|float $input): void
    {
        // Union types will be added in PHP 8
    }
}

В системі типів PHP все ще відсутня одна важлива фіча: generics . Є надія, що вони в швидкості будуть додані. У випадку з типізованими масивами вам доведеться покладатися на docblocks , щоб отримати належну підтримку IDE:

/** @var int[] */
public array $arrayOfInts = []; 

Синтаксис

Ера 7. * зробила багато хороших речей для перетворення PHP в більш зрілу мову. Нижче представлені деякі приклади.

Розкладання масиву:

[$a, $b] = $array;

Null-коалесцентний оператор:

$value = $object->property ?? 'fallback if null';

$value = $array['foo'] ?? "fallback if key doesn't exists"; 

Null-коалесцентний оператор присвоювання:

public function get(string $input): string 
{
    return $this->cache[$input] ??= $this->sanitize($input);
}

Поширення масиву:

$a = [/* … */];
$b = [/* … */];

$mergedArray = [...$a, ...$b];

Варіативні функції:

public function get(Foo ...$foos): void
{
    foreach($foos as $foo) {
        // …
    }
}

Розпакування аргументу: 

$this->get(...$arrayOfFoo);

Типізовані властивості:

public int $intProperty;

Стрілочні функції:

$ids = array_map(fn(Post $post): int => $post->id, $posts);

Генератори:

function make(array $input): Generator
{
    foreach ($input as $item) {
        yield $this->doSomethingWith($item);
    }
}

Ми розглянули поточні справи PHP, а тепер перейдемо до того, що нас чекає з виходом вісімки.

Об'єднані типи

З огляду на динамічно типізовану природу PHP, є багато випадків, коли бувають корисні об'єднані типи - набори з двох або більше типів.

Зверніть увагу, що void ніколи не може бути частиною об'єднаних типів, так як він вказує на повну відсутність значення, що повертається. Крім того, обнуляємі ( nullable) типи можуть бути записані за допомогою | null або за допомогою такої наступної нотації:

public function foo(Foo|Bar $input): int|float;

JIT

JIT - Just In Time-компілятор обіцяє значне поліпшення продуктивності, хоча і не завжди в контексті веб-запитів. На даній темі ми пропонуємо зупинитися докладніше.

JIT реалізований як частина розширення Opcache і потрібен для компіляції коду в інструкції процесора в рантаймі. Більше не доведеться інтерпретувати код за допомогою Zend VM - інструкції будуть виконуватися безпосередньо на рівні процесора. Щоб все краще зрозуміти, потрібно подумати про те, як PHP працює всередині.

Як виконується PHP-код?

Ми всі знаємо, що PHP - інтерпретована мова. Всякий раз, коли код виконується, потрібно пройти через інтерпретатор: PHP-FPM або CLI. Їх робота очевидна: отримати код, інтерпретувати, повернути результат. Детальніше:

  1. PHP-код зчитується і перетворюється в набір ключових слів, відомих як токени. Цей процес дозволяє інтерпретатору зрозуміти, який фрагмент коду написаний в якій частині програми. Цей крок називається токенізацією.
  2. Інтерпретатор аналізує колекцію токенів і намагається витягти з них сенс. В результаті синтаксичного аналізу генерується абстрактне синтаксичне дерево ( AST ), що представляє набір вузлів, що вказують, які операції повинні бути виконані.
  3. Перетворення AST в щось виконуване, вимагає проміжного представлення (IR), яке в PHP називається Opcode, а процес перетворення - компіляцією.
  4. Zend VM отримує список Opcod-ів і виконує їх.

Тут є вузьке місце: який сенс токенізувати і парсити код кожного разу, якщо він так часто не змінюється? Нас турбує тільки Opcode. І тут з'являється розширення Opcache.

Розширення Opcache

Дане розширення поставляється разом з PHP і додає в пам'ять загальний рівень кеша для Opcod-ів. Його завдання - за участю preloading взяти згенеровані Opcod-и з AST і закешити, щоб подальше виконання могло пройти без токенізаціі і синтаксичного аналізу.

Що ж робить JIT таким ефективним?

Zend VM - це програма, написана на мові Сі, яка діє як прошарок між Opcode-ми і CPU. JIT генерує скомпільований код в рантаймі, щоб PHP міг пропустити передачу в Zend.

У PHP JIT-реалізація використовує лібу DynASM ( Dynamic Assembler ), яка перетворює Opcod-и в специфічний для архітектури машинний код. А ще, PHP слабо типізований, і він не знає, який тип має змінна, поки Zend VM не виконає код.

Як поводиться JIT?

Ми знаємо, що з'ясувати типи заздалегідь ми не можемо і що компіляція в рантаймі ресурсовитратна. Щоб врівноважити ситуацію, JIT намагається скомпілювати тільки кілька Opcod-ів, які, на його думку, можуть окупитися (спираючись на ваш конфіг).

Коли певний код операції компілюється, JIT делегує йому виконання замість Zend VM:

Таким чином, в розширенні Opcache є пара інструкцій, що визначають, чи слід компілювати певний код операції чи ні. Якщо так, то компілятор перетворює код операції в машинний код за допомогою DynASM і виконує його.

Цікаво те, що оскільки в поточній реалізації існує обмеження в мегабайтах (це налаштовується), для скомпільованого коду виконання повинно легко перемикатися між JIT і інтерпритуємим  кодом.

Чи буде приріст продуктивності?

Сподіваємося, що стало зрозуміліше, чому більшість PHP-додатків не отримають великих переваг продуктивності від використання компілятора Just In Time.

Все тому, що JIT оптимізує операції, пов'язані з CPU, а більшість PHP-додатків в даний час більше пов'язані з I/O . Не має значення, чи компілюються операції обробки, якщо в процесі роботи доведеться звертатися до диска або мережі.

Якщо ви використовуєте JIT, наприклад, в обробці зображень або машинному навчанні, і там немає заточеного під I/O - JIT виграє у стандартного компілятора.

:: class для об'єктів

Невелика, але корисна нова функція: тепер можна використовувати :: class для об'єктів, замість використання get_class(). Він працює так само, як get_class():

$foo = new Foo();

var_dump($foo::class);

Статичний тип повертається 

Статичний тип повертається і буде корисний багатьом розробникам, особливо при наявності динамічної типізації:

class Foo
{
    public function test(): static
    {
        return new static();
    }
}

Weak maps (Слабкі карти)

Все починалося з weakrefs RFC, яка була додана в PHP 7.4, але реалізація WeakMap додана тільки лише в PHP 8. Слабкі карти містять посилання на об'єкти, які не перешкоджають збору сміття цих об'єктів.

Наприклад ORM, вони часто реалізують кеші, які містять посилання на класи сутностей для поліпшення продуктивності  між сутностями. Ці об'єкти не можуть бути "очищені від сміття", до тих пір, поки в кеші є посилання на них.

Велика плюшка слабких карт - можливість використання більш ресурсо-дружніх способів роботи з цими об'єктами. Ось як виглядають слабкі карти:

class Foo 
{
    private WeakMap $cache;
 
    public function getSomethingWithCaching(object $obj): object
    {
        return $this->cache[$obj]
           ??= $this->computeSomethingExpensive($obj);
    }
}

Stringable інтерфейс

Stringtable може використовуватися для введення підказок на все, що є рядком або реалізує __toString(). Крім того, кожного разу, коли клас реалізує __toString(), автоматично реалізується і інтерфейс, що виключає необхідність реалізовувати його вручну.    

class Foo
{
    public function __toString(): string
    {
        return 'foo';
    }
}

function bar(Stringable $stringable) { /* … */ }

bar(new Foo());
bar('abc');

Створення DateTime-об'єкта з інтерфейсу

Ви можете створити об'єкт DateTime з об'єкта DateTimeImmutable, використовуючи DateTime::create From Immutable($immutableDateTime. Шляхом додавання типу DateTime::createFromInterface() і DatetimeImmutable::createFromInterface() є узагальнений спосіб для перетворення DateTimeі об'єктів DateTimeImmutable один в одного.   

DateTime::createFromInterface(DateTimeInterface $other);

DateTimeImmutable::createFromInterface(DateTimeInterface $other);

Функція fdiv

Функція fdiv робить щось схоже на fmod і intdiv, що дозволяє ділити на 0. Замість помилок ви отримаєте INF, -INF або NAN , в залежності від ситуації.

Критичні зміни

Як уже згадувалося раніше, це велике оновлення, а значить, будуть серйозні зміни. Дізнатися повний список критичних змін можна в UPGRADING-файлі.

Багато з цих змін були відхилені в попередніх 7. * версіях, проте, якщо ви регулярно оновлювалися, перехід на PHP 8 не повинен викликати проблем.

Пріоритет конкатенації

Дана зміна застаріла в PHP 7.4, але воно знову в строю. Якби ви написали що-небудь таке:

echo "sum: " . $a + $b;

Раніше PHP розпізнав би це так:

echo ("sum: " . $a) + $b;

А тепер це інтерпретується наступним чином:

echo "sum: " . ($a + $b);

Послідовні помилки

Призначені для користувача функції в PHP вже викидають TypeErrors , але внутрішні функції цього не робили, вони повертали попередження або null. Починаючи з PHP 8 поведінка внутрішніх функцій стала іншою.

Звіт про помилки за замовчуванням

Тепер це E_ALL замість E_NOTICE і E_DEPRECATED. Це означає, що може проявитися багато помилок, які раніше тихенько собі сиділи і ігнорувалися, хоча, вже існували до PHP 8.

Оператор @ більше не буде мовчати

Ця зміна може виявити помилки, які були приховані до PHP 8. Обов'язково встановіть display_errors=off на ваших продакшен-серверах.

Зміна сигнатури методів

Змінено три сигнатури методів класів:

ReflectionClass::newInstance($args);
ReflectionFunction::invoke($args);
ReflectionMethod::invoke($object, $args);   

Тепер стало так:

ReflectionClass::newInstance(...$args);
ReflectionFunction::invoke(...$args);
ReflectionMethod::invoke($object, ...$args);

У керівництві по оновленню сказано, що якщо ви розширюєте ці класи і хочете використовувати як PHP 7, так і PHP 8, то допускаються наступні сигнатури:

ReflectionClass::newInstance($arg = null, ...$args);
ReflectionFunction::invoke($arg = null, ...$args);
ReflectionMethod::invoke($object, $arg = null, ...$args); 

Ми розглянули кілька змін PHP приблизно за останні десять років. Все йде до того, що восьма версія стане величезним кроком вперед.

Джерело перекладу


0 комментариев
Сортировка:
Добавить комментарий

IT Новости