DEV Community

Андрей Викулов (VProger)
Андрей Викулов (VProger)

Posted on • Originally published at viku-lov.ru on

PHP 8.3–8.4 для Bitrix и WordPress: типизация, атрибуты, паттерны

PHP 8.3–8.4 для Bitrix и WordPress: типизация, атрибуты, паттерны

PHP 8.3–8.4 для Bitrix и WordPress: типизация, атрибуты и модернизация легаси

Если вы до сих пор на PHP 7.4 в Bitrix/WordPress — вы живёте на пороховой бочке: часть библиотек уже «не разговаривает» с 7.4, а часть уязвимостей закрывают только в новых ветках. Смысл апгрейда не в “модно”, а в том, что вы начинаете ловить баги раньше , а не в проде в 03:00.

PHP 8.3 — про удобства и безопасность типизации (плюс мелкие приятности). (php.net/releases/8.3/)

PHP 8.4 — уже про новый уровень модели данных: property hooks и асимметричная видимость свойств. (php.net/releases/8.4/)

Ниже — без философии , только то, что вы реально начнёте применять в Bitrix (D7/ORM/events) и WordPress (WP_Query/hooks).


1) Union types: перестаём притворяться, что всё — “mixed”

Union types появились раньше (PHP 8.0), но именно при миграции с 7.4 они дают максимальную пользу: вы «подсвечиваете» границы легаси-кода и видите, где у вас вечная каша.

Bitrix: аккуратнее с тем, что “то строка, то число, то false”

Классика: опции, свойства, поля инфоблоков, где на выходе что угодно.

Было (7.4-стайл):


/\* _@return mixed_ /

function getOption($name) {

return \Bitrix\Main\Config\Option::get('main', $name);

}

Enter fullscreen mode Exit fullscreen mode

Стало (8.x):


use Bitrix\Main\Config\Option;

function getOption(string $name): string|int|bool|null

{

$value = Option::get('main', $name, null);

if ($value === null || $value === '') {

return null;

}

// пример: флажок

if ($value === 'Y' || $value === 'N') {

return $value === 'Y';

}

// пример: число

if (ctype\_digit($value)) {

return (int)$value;

}

return $value;

}

Enter fullscreen mode Exit fullscreen mode

Профит: дальше по коду вы не делаете «магические» сравнения, а IDE начинает реально помогать с подсказками и проверкой типов.

WordPress: функции/хуки, которые возвращают “строку или WP_Error”

Типичный случай: get_option(), get_post(), хуки фильтров — возвращают то объект, то false/WP_Error. Явный union type убирает догадки и даёт IDE возможность подсказывать поля.


function vp\_get\_post(int $postId): \WP\_Post|\WP\_Error

{

$post = get\_post($postId);

return $post instanceof \WP\_Post ? $post : new \WP\_Error('not\_found', 'Post not found');

}

// Использование — IDE знает тип, можно безопасно разветвить:

$postOrError = vp\_get\_post(123);

if (is\_wp\_error($postOrError)) {

error\_log($postOrError->get\_error\_message());

return;

}

// здесь $postOrError — гарантированно \WP\_Post

echo $postOrError->post\_title;

Enter fullscreen mode Exit fullscreen mode

2) Named arguments: меньше “простыней” и меньше ошибок в WP_Query/Bitrix API

Named arguments — не про красоту. Они про то, что вы не перепутаете параметры местами.

WordPress: WP_Query без “угадай, что за массив”

WP_Query принимает массив аргументов. Но “именованные аргументы” отлично заходят там, где у вас свои функции-обёртки.


function vp\_query\_posts(

int $limit = 10,

string $postType = 'post',

int $paged = 1,

): \WP\_Query {

return new \WP\_Query([

'post\_type' => $postType,

'posts\_per\_page' => $limit,

'paged' => $paged,

'no\_found\_rows' => true,

]);

}

// Читаемо:

$q = vp\_query\_posts(limit: 12, postType: 'product', paged: 2);

Enter fullscreen mode Exit fullscreen mode

Документация по WP_Query (аргументы/поведение) — Developer Resources.

Bitrix: когда у вас 3–4 флага в методе, именованные аргументы спасают


function vp\_syncOrder(int $orderId, bool $dryRun = false, bool $force = false): void

{

// ...

}

vp\_syncOrder(orderId: 123, force: true);

Enter fullscreen mode Exit fullscreen mode

3) Match expressions: нормальный маппинг вместо switch-помойки

match (PHP 8.0) — must-have для маппинга статусов, типов, ролей.

Bitrix: маппинг статуса заказа → стадия сделки

Событие заказа в Bitrix: OnSaleOrderSaved (уже после сохранения сущностей). (API Bitrix24)


use Bitrix\Sale\Order;

function mapOrderStatusToDealStage(string $statusId): string

{

return match ($statusId) {

'N' => 'NEW',

'P' => 'PREPARATION',

'F' => 'WON',

'C' => 'LOSE',

default => 'NEW',

};

}

Enter fullscreen mode Exit fullscreen mode

WordPress: маппинг режима запроса


function vp\_query\_args(string $mode): array

{

return match ($mode) {

'latest' => ['orderby' => 'date', 'order' => 'DESC'],

'popular' => ['orderby' => 'comment\_count', 'order' => 'DESC'],

default => ['orderby' => 'date', 'order' => 'DESC'],

};

}

Enter fullscreen mode Exit fullscreen mode

4) Attributes (#[...]): меньше магии в докблоках, больше структуры

Атрибуты — это метаданные, которые можно читать рефлексией. В Bitrix и WordPress “из коробки” они не везде используются, но внутри вашего кода — это супер-замена разрозненным соглашениям.

4.1. PHP 8.3: #[\Override] — дешёвый способ поймать кривое наследование

В PHP 8.3 появился атрибут #[\Override]. (PHP.Watch)

Он делает простую вещь: если вы написали метод “как будто переопределяете”, но реально не переопределяете — получите ошибку.


class BaseHandler {

public function handle(): void {}

}

class OrderHandler extends BaseHandler {

#[\Override]

public function handle(): void {}

}

Enter fullscreen mode Exit fullscreen mode

Где полезно: Bitrix-модули/интеграции, где куча наследования и легко “промазать” сигнатурой.

4.2. WordPress: атрибуты для хуков (красиво, но аккуратно)

В WP сообщество делает библиотеки, которые регистрируют хуки через атрибуты (это не core-фича, а подход).

Смысл: вы видите в классе “что цепляется куда”, без простыни add_action().

Пример концепта (самописная реализация, 30 строк рефлексии — и поехали):


#[Attribute(Attribute::TARGET\_METHOD)]

class ActionHook {

public function \_\_construct(

public string $hook,

public int $priority = 10,

public int $acceptedArgs = 1,

) {}

}

final class Hooks {

public static function register(object $instance): void

{

$ref = new ReflectionObject($instance);

foreach ($ref->getMethods(ReflectionMethod::IS\_PUBLIC) as $method) {

foreach ($method->getAttributes(ActionHook::class) as $attr) {

/\* _@var ActionHook $meta_ /

$meta = $attr->newInstance();

add\_action(

$meta->hook,

[$instance, $method->getName()],

$meta->priority,

$meta->acceptedArgs

);

}

}

}

}

final class SeoTweaks {

#[ActionHook('wp\_head', priority: 1, acceptedArgs: 0)]

public function addMeta(): void

{

echo "<!-- SEO tweaks -->\n";

}

}

Hooks::register(new SeoTweaks());

Enter fullscreen mode Exit fullscreen mode

Честно: в проде это нужно не всем. Но если у вас большой плагин с 50+ хуками — это реально упорядочивает хаос.

4.3. Bitrix: DTO/валидаторы/маппинг — атрибуты заходят идеально

Bitrix D7 ORM сам по себе атрибуты не “понимает”, но вы можете строить аккуратные слои вокруг:


#[Attribute(Attribute::TARGET\_PROPERTY)]

class Required {}

final class CreateDealDTO

{

public function \_\_construct(

#[Required] public string $title,

public int $assignedById = 1,

) {}

}

function validate(object $dto): array

{

$errors = [];

$ref = new ReflectionObject($dto);

foreach ($ref->getProperties() as $prop) {

$isRequired = !empty($prop->getAttributes(Required::class));

if (!$isRequired) continue;

$prop->setAccessible(true);

$val = $prop->getValue($dto);

if ($val === null || $val === '' ) {

$errors[] = $prop->getName() . ' is required';

}

}

return $errors;

}

Enter fullscreen mode Exit fullscreen mode

5) Fibers: да, они существуют. Нет, они не “ускорят сайт” магически

Fibers (PHP 8.1) — низкоуровневая штука для кооперативной многозадачности. В Bitrix/WordPress в типичном HTTP-запросе вы почти никогда не должны ими лечить проблемы.

Когда fibers реально уместны:

  • CLI-скрипты, воркеры, очереди (где вы контролируете цикл).
  • Интеграции с async IO через библиотеки/рантаймы (ReactPHP, Amp и т.д.).

Мини-идея для воркера: “параллельно” обработать пачку задач, не плодя процессы:


$fibers = array\_map(

fn(int $id) => new Fiber(function () use ($id) {

// simulate work

return "done:$id";

}),

[1,2,3,4]

);

$results = [];

foreach ($fibers as $fiber) {

$results[] = $fiber->start();

}

var\_dump($results);

Enter fullscreen mode Exit fullscreen mode

Если у вас “gap в backend” (узкие места): начните не с fibers, а с банального:

  • убрать N+1 запросы,
  • включить нормальный кеш,
  • перестать делать внешние HTTP-запросы в критическом пути,
  • вынести тяжёлое в агенты/cron/очередь.

Кстати, если тема автоматизации в Bitrix актуальна — вот базовый материал: https://viku-lov.ru/blog/backend-cron-bitrix-agents-automation


6) PHP 8.4: property hooks и асимметричная видимость — убийцы “геттеров ради геттеров”

6.1. Asymmetric visibility: “читать всем, писать только классу”

Официально: в 8.4 можно разделять видимость get/set. (RFC Asymmetric Visibility)


final class OrderView

{

public string $status;

public private(set) int $id;

public function \_\_construct(int $id, string $status)

{

$this->id = $id; // можно тут

$this->status = $status;

}

}

// где-то снаружи:

$view = new OrderView(10, 'N');

echo $view->id; // ок

$view->id = 99; // нельзя

Enter fullscreen mode Exit fullscreen mode

Для Bitrix/WordPress это прямой профит в DTO/ViewModel: меньше “случайных” мутаций.

6.2. Property hooks: логика “на свойстве”, а не в 10 методах

PHP 8.4: property hooks. (php.net/releases/8.4/)

Это полезно, когда у вас в легаси-коде тонны однотипных getX()/setX().

Условный пример: нормализация телефона:


final class Contact

{

public string $phone {

set => preg\_replace('~\D+~', '', $value);

}

public function \_\_construct(string $phone)

{

$this->phone = $phone;

}

}

$c = new Contact('+49 (151) 123-45-67');

echo $c->phone; // 491511234567

Enter fullscreen mode Exit fullscreen mode

Где реально заходит:

  • Bitrix: сущности интеграций, DTO для CRM/1С, преобразование входных данных.
  • WordPress: настройки плагина, sanitize/normalize рядом с данными.

7) Реальные примеры “modernize” для Bitrix и WordPress

7.1. Bitrix: обработчик события заказа + строгая сигнатура + match

Событие OnSaleOrderSaved документировано в Bitrix24 API.


// /local/php\_interface/init.php

use Bitrix\Main\Loader;

Loader::includeModule('sale');

AddEventHandler('sale', 'OnSaleOrderSaved', static function(\Bitrix\Main\Event $event) {

/\* _@var \Bitrix\Sale\Order $order_ /

$order = $event->getParameter('ENTITY');

if (!$order) return;

$statusId = (string)$order->getField('STATUS\_ID');

$dealStage = mapOrderStatusToDealStage($statusId);

// дальше — обновляете CRM сделку как у вас принято

});

Enter fullscreen mode Exit fullscreen mode

AddEventHandler обычно подключают в /bitrix/php_interface/init.php или /local/php_interface/init.php. (документация Bitrix)

7.2. Bitrix ORM: более предсказуемые типы на границе

Пример ORM-стиля D7 (близко к реальности). (Bitrix D7 ORM)


use Bitrix\Main\Loader;

use Bitrix\Crm\DealTable;

Loader::includeModule('crm');

function getDealTitle(int $dealId): string|null

{

$row = DealTable::getList([

'select' => ['ID', 'TITLE'],

'filter' => ['=ID' => $dealId],

'limit' => 1,

])->fetch();

return $row ? (string)$row['TITLE'] : null;

}

Enter fullscreen mode Exit fullscreen mode

7.3. WordPress: WP_Query + “тонкая” обёртка с типами


/\*\*

- @return array<int, \WP\_Post>

\*/

function vp\_latest\_posts(int $limit = 10): array

{

$q = new \WP\_Query([

'post\_type' => 'post',

'posts\_per\_page' => $limit,

'no\_found\_rows' => true,

]);

return $q->posts ?: [];

}

Enter fullscreen mode Exit fullscreen mode

8) План постепенной модернизации легаси-кода (без “переписать всё”)

Шаг 0. Подготовка: стенд и контроль

  • Поднимите второй PHP (8.3/8.4) на staging/локалке.
  • Включите строгие ошибки на стенде:

  • error_reporting(E_ALL);

  • логи в файл

  • Прогоните “карту боли”: какие плагины/модули умирают первыми.

Шаг 1. Совместимость и зависимости

  • Обновите Composer-зависимости под PHP 8.x.
  • В WordPress проверьте плагины/темы на совместимость.
  • В Bitrix проверьте кастомные модули и правки шаблонов.

Шаг 2. Типизация по границам

Не пытайтесь типизировать весь проект. Начните с границ:

  • входные DTO,
  • сервисы интеграции,
  • функции-обёртки над API Bitrix/WP,
  • всё, что "вечно падает".

Минимальный пример «границы» — обёртка над опцией WordPress с явным возвращаемым типом:


// Было: непонятно, что вернётся

function get\_site\_option($name) { return get\_option($name); }

// Стало: контракт на границе

function get\_site\_option(string $name): string|int|bool|null

{

$val = get\_option($name);

if ($val === false) return null;

return $val;

}

Enter fullscreen mode Exit fullscreen mode

Шаг 3. Рефакторинг “мест, где больно”

Топ-цели:

  • match для маппинга,
  • union types для возвратов,
  • named arguments для читаемости,
  • #[\Override] в местах наследования.

Шаг 4. 8.4-фишки — точечно

  • Asymmetric visibility — почти всегда безопасно и полезно.
  • Property hooks — только там, где реально десятки одинаковых геттеров/сеттеров.

9) 7 признаков, что у вас древний PHP (и как чинить)

  1. Везде array() и “магические массивы” вместо структур
  • ✅ Исправление: DTO + типы возврата + named arguments в обёртках.
  1. Методы возвращают “что угодно”: false|string|array|int
  • ✅ Исправление: union types + нормализация (как в getOption() выше).
  1. switch на 200 строк для статусов/типов
  • ✅ Исправление: match.
  1. Наследование “на честном слове”, постоянно ломают сигнатуры
  1. Код полагается на докблоки, а IDE всё равно врёт
  • ✅ Исправление: реальные типы параметров/возвратов + простые DTO.
  1. Тяжёлая логика выполняется в HTTP-запросе
  1. “У нас нет тестов, но мы уверены”
  • ✅ Исправление: хотя бы smoke-тесты/минимальные PHPUnit-тесты на критические сервисы + прогон линтера в CI.

10) Мини-чеклист миграции 7.4 → 8.3/8.4 для Bitrix/WordPress

  • [] Отдельный стенд с PHP 8.3+
  • [] Обновлены зависимости/плагины/модули
  • [] Включены логи и E_ALL на стенде
  • [] Типизация добавлена на границах (DTO/сервисы/обёртки)
  • [] match заменил “switch-ад”
  • [] #[\Override] поставлен в местах наследования
  • [] 8.4-фишки (asymmetric visibility / hooks) применены точечно и осознанно

Связанные сниппеты

Внутренние ссылки (в тему)

Read more on viku-lov.ru

Top comments (0)