PHP 7.4: Изменения

Опубликовано 0 комментариев 2447 просмотров
PHP 7.4: Изменения

Прошло совсем немного времени с релиза текущей актуальной версии PHP 7.3, а комьюнити и команда разработчиков языка уже вовсю работает над списком изменений для следующей версии - PHP 7.4.

По аналогии с чейнжлогом PHP 7.3 данный пост будет дополняться до тех пор, пока не состоится официальный релиз стабильной версии PHP 7.4. С учетом цикла разработки это произойдет в ноябре-декабре 2019 года. Стоит также напомнить, что уже 30 ноября 2019 года будет окончательно прекращен выпуск обновлений (кроме обновлений безопасности) для PHP 7.1 поэтому для всех, кто использует эту или, что еще хуже, более ранние версии языка в своих проектах настало время апгрейда.

Список изменений

Предварительная загрузка

Данное нововведение базируется на технологии "Class Data Sharing" от Java HotSpot VM. Оно предоставляет пользователю возможность использовать гибкость кэширования, предоставляемого PHP (APC, Turck MMCache, Zend OpCache) для увеличения производительности собственных приложений. При запуске сервера, перед запуском любого приложения появится возможность скомпилировать и сохранить в памяти определенный набор файлов PHP, сделав их доступными сразу при последующих запросах. Все функции и классы из этих файлов будут доступны для использования прямо из коробки абсолютно так же, как и внутренние объекты (\Exception или strlen(), например).

Таким образом появится возможность предзагрузки целых фреймворков, библиотек. Это также позволит добавлять “встроеннные” функции, написанные на PHP (по аналогии с HHVM sytemlib).

Предварительная загрузка будет контролироваться новой директивой php.ini - opcache.preload. В ней будет указан путь к файлу PHP, который будет загружен в память. Этот файл может загружать другие файлы, используя их или функцию opcache_compile_file().

Например, этот файл добавляет новую функцию и использует ее для загрузки всего Zend Framework.

function _preload($preload, string $pattern = '/\.php$/', array $ignore = [])
{
  if(is_array($preload)) {
    foreach($preload as $path) {
      _preload($path, $pattern, $ignore);
    }
  } elseif(is_string($preload)) {
    $path = $preload;
    if(!in_array($path, $ignore)) {
      if(is_dir($path)) {
        if($dh = opendir($path)) {
          while(($file = readdir($dh)) !== false) {
            if($file !== '.' && $file !== '..') {
              _preload($path.'/'.$file, $pattern, $ignore);
            }
          }

          closedir($dh);
        }
      } elseif(is_file($path) && preg_match($pattern, $path)) {
        if(!opcache_compile_file($path)) {
          trigger_error('Preloading Failed', E_USER_ERROR);
        }
      }
    }
  }
}

set_include_path(get_include_path().PATH_SEPARATOR.realpath('/var/www/ZendFramework/library'));

_preload(['/var/www/ZendFramework/library']);

Предварительно загруженный файл остается в кэше навсегда и для его обновления потребуется перезагрузка сервера. opcache_reset() не сможет и не будет перезагружать предварительно загруженные файлы, а opcache_get_status() будет дополнен для предоставления информации о предварительно загруженных функция, классах и скриптах в индексе preload_statistics.

Кроме того, во избежание недоразумений не будет изменяться поведение статических данных классов. В предварительной загрузке смогут участвовать только классы с доступным родителем и интерфейсом, без динамических переменных, - иначе будет вызвано поведение как для класса без предварительной загрузки.

В Windows невозможно будет предварительно загрузить классы, унаследованные от внутренних.

Хэш-расширение всегда доступно

Расширение хэша ext/hash будет всегда доступно подобно date, spl и pcre.

Аргумент –enable-hash, используемый в конфигурации сборки удален.

Реестр хэш-расширений

Будет добавлен механизм реестра хэш-расширений password_algos().

print_r(password_algos());

Array(
    [0] => "2y" // Ident for "bcrypt"
    [1] => "argon2i"
    [2] => "argon2id"
)

Типизированные свойства

7 версия языка добавила возможность указывать скалярный или структурированный тип для аргументов функций и методов, а также скалярный тип для возвращаемых ими значений. PHP 7.4 дополнит это изменение возможностью указывать типы свойств классов.

class Test {
    // Legal default values
    public bool $a = true;
    public int $b = 42;
    public float $c = 42.42;
    public float $d = 42; // Special exemption
    public string $e = "str";
    public array $f = [1, 2, 3];
    public iterable $g = [1, 2, 3];
    public ?int $h = null;
    public ?object $i = null;
    public ?Test $j = null;

    // These have *no* legal default values
    public object $k;
    public Test $l;

    // ILLEGAL default values
    public bool $m = 1;
    public int $n = null;
    public Test $o = null;
}

Изменения openssl_random_pseudo_bytes()

В случае ошибки генерации строки в openssl_random_pseudo_bytes() будет вызван \Exception с описанием проблемы. Присваемое второму аргументу $crypto_strong значение гарантированно будет true в случае, если не сработал механизм вызова ошибки.

FFI - Интерфейс для внешних функций на C

FFI - полезная фича из Python и LuaJIT, позволяющая работать с функциями и данными C из скриптового языка. Для PHP FFI открывает способ написания расширений и привязок PHP к библиотекам C на чистом PHP.

// create FFI object, loading libc and exporting function printf()
$ffi = FFI::cdef(
    "int printf(const char *format, ...);", // this is regular C declaration
    "libc.so.6"
);

// call C printf()
$ffi->printf("Hello %s!\n", "world");

Для минимизации рисков использование данного интерфейса будет определяться директивой php.ini ffi.enable - false отключить, true включить везде и preload (по-умолчанию) разрешить использование только в файле предварительной загрузки.

У класса FFI доступны следующие методы:

  • FFI::cdef([string $cdef = "" [, string $lib = null]]): FFI
  • FFI::new(mixed $type [, bool $own = true [, bool $persistent = false]]): FFI\CData
  • FFI::free(FFI\CData $cdata): void
  • FFI::cast(mixed $type, FFI\CData $cdata): FFI\CData
  • FFI::addr(FFI\CData $cdata): FFI\CData
  • FFI::type(string $type): FFI\CType
  • FFI::arrayType(FFI\CType $type, array $dims): FFI\CType
  • FFI::typeof(FFI\CData $data): FFI\CType
  • FFI::sizeof(mixed $cdata_or_ctype): int
  • FFI::alignof(mixed $cdata_or_ctype): int
  • FFI::memcpy(FFI\CData $dst, mixed $src, int $size): void
  • FFI::memcmp(mixed $src1, mixed $src2, int $size): int
  • FFI::memset(FFI\CData $dst, int $c, int $size): void
  • FFI::string(FFI\CData $src [, int $size]): string
  • FFI::load(string $file_name): FFI
  • FFI::scope(string $scope_name): FFI

Более подробно о доступных методах можно прочитать в RFC.

Использование FFI в коде не увеличит его скорость и даже наоборот. Тем не менее, есть смысл использовать данный функционал там, где необходимо уменьшить потребление памяти.

Native FFI
Python 0.212 0.343
PyPy 0.010 0.081
LuaJit -joff 0.037 0.412
LuaJit -jon 0.003 0.002
PHP 0.040 0.093
PHP + jit 0.016 0.087

Комбинированный оператор слияния с null

В PHP 7 добавилась возможность указывать значение в виде $foo ?? 'baz', что звучит как "если $foo существует и не равен null присваивается $foo, иначе 'baz'". Теперь отсутствует необходимость дублировать переменную при проверке через isset($foo) ? $foo : 'baz', что немного облегчило процесс чтения кода.

В PHP 7.4 появится возможность использовать синтаксис "если левый параметр не существует или равен null, присвоить ему значение правого параметра". В коде это выглядит так:

// Было
$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';

// Стало
$this->request->data['comments']['user_id'] ??= 'value';

Включение mb_str_split() в mbstring

mb_str_split() - новая функция, разбивающая многобайтовую строку на чанки по n символов каждый в PHP 7.4 станет частью расширения mbstring.

array mb_str_split (string $string [, integer $split_length = 1, string $encoding = mb_internal_encoding() ])
print_r(mb_str_split('победа', 2));

Array
(
    [0] => по
    [1] => бе
    [2] => да
)

Рефлексия для ссылок

Появится новый глобальный класс, часть Reflection API - ReflectionReference. По сути он будет работать как spl_object_hash(), но отвечать за работу с ссылками а не объектами.

final class ReflectionReference {
    /* Returns ReflectionReference if array element is a reference, null otherwise. */
    public static function fromArrayElement(array $array, int|string $key): ?ReflectionReference;

    /* Returns unique identifier for the reference. The return value format is unspecified. */
    public function getId(): int|string;

    private function __construct(); // Always throws
    private function __clone(); // Always throws
}

Доступный изначально статический метод fromArrayElement(array $array, int|string $key) вернет экземпляр ReflectionReference если $array[$key] является ссылкой, иначе null.

Если $array не является массивом или $key не строка или число, вызовется TypeError. В сценарии, когда $array[$key] не существует будет вызвано исключение ReflectionException.

Новый механизм сериализации объектов

Сегодня PHP предлагает два механизма для настраиваемой сериализации объекта - магические методы __sleep()/__wakeup() и интерфейс Serializable. PHP 7.4 добавится новый механизм, пытающийся совместить в себе универсальность Serializable и подход к реализации __sleep()/__wakeup().

Работа будет происходить через 2 новых метода:

// Returns array containing all the necessary state of the object.
public function __serialize(): array;

// Restores the object state from the given data array.
public function __unserialize(array $data): void;

Они очень похожи на Serializable. Отличие от интерфейса состоит в том, что вместо сериализованных строк работа происходит с исходными массивами.

class Foo
{
    private $baz;

    public function __serialize(): array 
    {
        return ["baz" => $this->baz];
    }

    public function __unserialize(array $data) 
    {
        $this->baz = $data["baz"];
    }
}

Стрелочные функции

Появится новый вид функций, аналог анонимных - "стрелочные". Грубо говоря, это однострочная анонимная функция с облегченным синтаксисом.

Синтаксис выглядит как fn(arguments) => expression, где fn - ключевое триггер-слово (обратите внимание, что для обратной совместимости необходимо будет избавиться от функций с аналогичным названием), arguments - список передаваемых аргументов, expression - "тело" функции. use (arguments) объявлять не требуется.

$y = 1;

// анонимная функция
$fn1 = function ($x) use ($y) {
    return $x + $y;
};

// стрелочная функция
$fn2 = fn($x) => $x + $y;

Стрелочные функции поддерживают типизацию.

fn(array $x) => $x;
fn(): int => $x;
fn($x = 42) => $x;
fn(&$x) => $x;
fn&($x) => $x;
fn($x, ...$rest) => $rest;

В теле функции можно использовать классовый $this. В случае необходимости ограничения можно запретить поведение указанием static перед функцией.

class Test {
    public function method() {
        $fn = fn() => var_dump($this);
        $fn(); // object(Test)#1 { ... }

        $fn = static fn() => var_dump($this);
        $fn(); // Error: Using $this when not in object context
    }
}

Левоассоциативный тернарный оператор объявлен устаревшим и удален

В то время как в большинстве языков программирования обработка тернарного оператора происходит справа налево, в PHP это происходит ровно наоборот. Такой синтаксис вкупе с множеством вложенных условий может сильно сбить разработчиков. В PHP 7.4 такое поведение объявлено устаревшим и удалено. Взамен при множестве условий предлагается использовать скобки.

1 ? 2 : 3 ? 4 : 5;   // deprecated
(1 ? 2 : 3) ? 4 : 5; // ok
1 ? 2 : (3 ? 4 : 5); // ok

1 ?: 2 ? 3 : 4;   // deprecated
(1 ?: 2) ? 3 : 4; // ok
1 ?: (2 ? 3 : 4); // ok

В качестве исключения скобки не требуются при объединении двух коротких тернарных операторов а также при вложении в средний операнд.

1 ?: 2 ?: 3;   // ok
(1 ?: 2) ?: 3; // ok
1 ?: (2 ?: 3); // ok

1 ? 2 ? 3 : 4 : 5 // ok
1 ? 2 ?: 3 : 4    // ok

Оператор переменного количества в массивах

Известный ранее по аргументам функций синтаксис foo(..$args) теперь доступен для использования в массивах.

$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// ['banana', 'orange', 'apple', 'pear', 'watermelon'];

Остается невозможным использование конструкции в пустом массиве.

$arr1 = [1, 2, 3];
$arr2 = [...&$arr1]; //invalid syntax

ext/interbase удален и доступен в PECL

Данное расширение, предназначенное для работы с БД InterBase и Firebird довольно давно "заброшено" и без поддержки комьюнити исправления и нововведения, которые могли бы появиться добавлялись бы "вслепую". В связи с этим принято удалить расширение из PHP 7.4 и опубликовать его в PECL.

Слабые ссылки

Слабые ссылки позволяют программисту сохранять ссылку на объект, которая не препятствует его уничтожению. Они полезны для реализации кеш-подобных структур. В настоящее время они поддерживаются в PHP расширением.

В новой версии PHP их поддержка будет доступна "из коробки" через добавленный класс.

final class WeakReference {
    public static function create(object $object) : WeakReference;

    public function get() : ?object;
}

Удаление ext/wddx

WDDX был создан как независимый от ЯП формат обмена данный для Web 1.0. Формально он не был стандартизирован и в текущих реалиях вполне может быть заменен другими форматами, такими как JSON.

В PHP 7.4 будет удален весь функционал, связанный с данным форматом.

Изменение приоритета оператора конкатенации

Сейчас приоритет операторов ., + и - равен. Любая их комбинация просто отрабатывает слева-направо.

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

// сейчас сначала в "sum: " будет довавлена $a, затем произойдет сложение
echo ("sum: " . $a) + $b;

В PHP 8.0 оператору . будет дан более низкий приоритет, из-за чего первыми будут обработаны операции сложения/вычитания.

// в php 8.0 сначала произойдет сложение $a и $b, затем сумма будет добавлена к "sum: "
echo "sum :" . ($a + $b);

В версии 7.4 при обнаружении строк, где комбинируется строковой оператор . c + или - без скобок будет вызвано Deprecated уведомление.

В ответ на сообщение

Доступна разметка Markdown. А еще вы можете использовать крутой пак эмоций.

Нажимая на кнопку «Отправить» вы даете свое согласие на обработку персональных данных в соответствии с законом №152-ФЗ «О персональных данных» от 27.07.2006 и принимаете условия Политики конфеденциальности.