Перейти к основному содержимому

5.07. PHP

Разработчику Архитектору

Основы языка

PHP — редкий пример языка, возникшего не из академических или системных потребностей, а из конкретной инженерной задачи: встраивание логики в HTML-страницы. Первая версия — Personal Home Page Tools (PHP/FI), написанная Расмусом Лердорфом в 1994 году — представляла собой набор CGI-скриптов на C, предназначенных для отслеживания посещений его резюме. Уже на этом этапе проявилась ключевая черта, определившая эволюцию PHP: ориентация на веб как на среду исполнения. В отличие от Perl, который тогда широко использовался для CGI, или Java Servlet, требовавших сложной инфраструктуры, PHP изначально задумывался как язык минимального сопротивления: встроенный в HTTP-сервер, без необходимости явного управления циклом запросов, с доступом ко всем параметрам запроса «из коробки».

С тех пор PHP прошёл путь от утилитарного препроцессора к полноценному языку общего назначения. Однако его архитектурная ДНК остаётся неизменной: язык не пытается абстрагироваться от HTTP, а, напротив, интегрирует его в своё ядро. Это определяет как сильные стороны (быстрое создание веб-интерфейсов, естественная работа с формами и сессиями), так и ограничения (избыточность веб-специфичных конструкций в CLI-контексте). Сегодня PHP — это не «язык для сайтов», а язык с сильной веб-специализацией, способный, при необходимости, выйти за её пределы.

Современное определение — PHP: Hypertext Preprocessor — уже является рекурсивным акронимом, что символично: язык описывает сам себя, постоянно переосмысливая и переписывая собственные основы (от процедурного стиля до строгой типизации и метапрограммирования), сохраняя при этом обратную совместимость как один из ключевых принципов.


Концептуальные основы: как устроен PHP «изнутри»

1. Жизненный цикл скрипта: от запроса к ответу

В отличие от долгоживущих серверных приложений (Node.js, Java), PHP по умолчанию работает в модели однократного исполнения. Каждый HTTP-запрос порождает отдельный процесс (или поток в SAPI-режиме), в котором:

  1. Инициализируется интерпретатор (загружаются расширения, OPcache, инициализируются суперглобальные массивы);
  2. Выполняется код PHP-файла (или цепочки файлов через include);
  3. Генерируется HTTP-ответ (заголовки, тело документа);
  4. Процесс завершается, освобождая всю память.

Эта модель упрощает управление состоянием (не нужно заботиться о памяти между запросами), но накладывает ограничения на долгие операции и кэширование в памяти. С появлением SAPI-интерфейсов вроде php-fpm и RoadRunner/Swoole, PHP приобрёл возможность работать и в долгоживущих режимах (worker-based), что расширило его применение в микросервисах и высоконагруженных API, однако классическая модель остаётся доминирующей и определяет философию ядра.

2. Интерпретация и оптимизация: OPcache как компромисс

Технически PHP не является чистым интерпретатором. Современный движок Zend Engine (начиная с PHP 7) преобразует исходный код в промежуточное представление — OPcode (операционный код), аналогичное байт-коду в Java или Python. Этот OPcode затем выполняется виртуальной машиной Zend VM.

Ключевая оптимизация — OPcache (включён по умолчанию с PHP 5.5) — кэширует скомпилированный OPcode в разделяемой памяти, устраняя повторную токенизацию и компиляцию при каждом запросе. Это превращает PHP в частично компилируемый язык: время запуска становится сопоставимым с JIT-компиляцией (особенно после PHP 8.0, где Zend Engine получил улучшенный оптимизатор), а сам код остаётся гибким и интерпретируемым на этапе разработки.

Важно подчеркнуть: OPcache не меняет семантику языка. Даже при кэшировании OPcode переменные остаются динамическими, типы — нефиксированными (если не включена строгая типизация), а поведение include — динамическим. OPcache — это ускорение инициализации, а не трансформация в статически типизированный компилируемый язык.

3. Динамическая типизация: гибкость и её стоимость

PHP — язык с динамической, слабой типизацией по умолчанию. Это означает, что:

  • Тип переменной не объявляется и определяется во время выполнения на основе значения;
  • Автоматические преобразования между типами происходят неявно в большинстве операций;
  • Одна и та же переменная может менять тип в ходе выполнения скрипта.

Эта модель обеспечивает высокую скорость прототипирования: разработчик может сосредоточиться на логике, не тратя время на декларацию типов и управление памятью. Однако она влечёт за собой две системные проблемы:

Во-первых, неопределённость поведения. Например:

echo "10 apples" + 2; // → 12 (строка преобразуется к числу по правилам "leading numeric string")
echo "apples 10" + 2; // → 2 (строка без ведущего числа → 0)

Предсказуемость требует глубокого знания правил преобразования (см. спецификацию convert_to_number() в Zend Engine).

Во-вторых, сложность статического анализа. Без явных типов IDE и анализаторы кода не могут гарантировать корректность вызовов. Для решения этой проблемы в PHP 7.0 была введена строгая типизация через директиву declare(strict_types=1), которая:

  • Применяется на уровне файла;
  • Требует точного совпадения типов аргументов и возвращаемых значений (исключения — null, если указан ?T);
  • Не отменяет динамическую природу языка, а лишь ограничивает её в определённых контекстах.

Строгая типизация — не замена динамической, а инструмент локального ужесточения в критически важных участках кода (например, в слое домена или API-контрактах). Это компромисс, характерный для PHP: сохранение гибкости в целом при предоставлении механизмов контроля там, где он необходим.

4. HTTP как часть языка

В PHP нет необходимости импортировать HTTP-библиотеки или инициализировать серверный объект — HTTP — это окружение по умолчанию. С момента старта скрипта доступны:

  • Суперглобальные массивы — прямой доступ к параметрам запроса и метаданным;
  • Встроенные функции для работы с заголовками (header()), статусами (http_response_code()), cookie (setcookie()), сессиями (session_start());
  • Автоматическая обработка multipart/form-data и помещение загруженных файлов в $_FILES.

Это достигается за счёт тесной интеграции с SAPI (Server API) — интерфейсом, через который PHP взаимодействует с веб-сервером (Apache, Nginx через php-fpm, CLI и др.). Каждый SAPI предоставляет свои реализации функций ввода-вывода, что делает поведение PHP контекст-зависимым. Например, header() в CLI-режиме вызывает ошибку, поскольку нет HTTP-ответа.

Такая интеграция — двусторонний меч: с одной стороны, она устраняет «бумажную работу» при создании веб-приложений; с другой — затрудняет тестирование (требуется мокать глобальные состояния) и усложняет архитектурную чистоту (логика зависит от глобального окружения). Современные фреймворки (Symfony, Laravel) решают эту проблему, инкапсулируя HTTP-взаимодействие в объекты запроса и ответа (Request, Response), но на уровне ядра PHP остаётся «серверным скриптовым языком».


Суперглобальные массивы: окна в окружение

Суперглобальные массивы — не просто удобные переменные, а артефакты архитектуры однозапросного исполнения. Поскольку между запросами нет постоянного состояния, каждый новый запрос должен получить полное описание контекста в виде структурированных данных. PHP предоставляет эти данные в виде предопределённых массивов, доступных в любой точке скрипта без объявления.

МассивИсточник данныхОсобенности
$_GETQuery string (URL после ?)Все значения — строки. Подвержен инъекциям (например, ?id[]=1&id[]=2 → массив).
$_POSTТело запроса с Content-Type: application/x-www-form-urlencoded или multipart/form-dataАвтоматически парсится PHP; для JSON требуется ручной json_decode(file_get_contents('php://input')).
$_COOKIEЗаголовок Cookie из запросаPHP не проверяет корректность имён/значений — ответственность за валидацию лежит на разработчике.
$_SESSIONХранилище (файлы, Redis, БД), идентифицируемое по PHPSESSID из cookie или URLТребует вызова session_start() до чтения/записи. Не является «глобальным» в классическом смысле — зависит от идентификатора сессии.
$_SERVERПеременные окружения + заголовки HTTP (префикс HTTP_) + метаданные запросаЕдинственный массив, содержащий как клиентские (HTTP_USER_AGENT), так и серверные (DOCUMENT_ROOT, SCRIPT_FILENAME) данные.
$_FILESТолько при Content-Type: multipart/form-dataСодержит структуру ['name', 'type', 'tmp_name', 'error', 'size'] для каждого поля <input type="file">.
$_REQUESTОбъединение $_GET, $_POST, $_COOKIE (в порядке, задаваемом variables_order)Из-за неопределённости источника не рекомендуется к использованию в production-коде.
$_ENVПеременные окружения процессаДоступны только при включённой директиве variables_order, включающей E.

Важнейшее замечание: суперглобальные — это не «данные пользователя», а «сырые входные данные». Они не проходят автоматической санитизации. Любой доступ к $_GET['id'] без валидации и экранирования — это потенциальная уязвимость (XSS, SQLi, LFI). Современная практика предписывает обёртывать их в контроллеры или DTO с явной валидацией, что, однако, остаётся задачей фреймворков, а не языка.


Синтаксическая эволюция: от Perl к современности

Синтаксис PHP унаследовал черты трёх языков:

  • C — управляющие конструкции (if, for, while), операторы (++, +=), фигурные скобки;
  • Perl — обработка строк ($var в двойных кавычках), регулярные выражения (PCRE), суперглобальные;
  • Java — синтаксис классов, интерфейсов, исключений (появился в PHP 5).

С течением времени PHP постепенно отходил от «скриптового» стиля к более строгому и выразительному:

  • PHP 5.3: анонимные функции, замыкания (use), пространства имён;
  • PHP 5.6: оператор ... (splat), константы const в классах;
  • PHP 7.0: строгая типизация, скалярные типы, возврат void, null coalescing (??);
  • PHP 7.4: стрелочные функции, typed properties;
  • PHP 8.0: union types, match-expression, JIT-компиляция (ограниченно), nullsafe operator (?->);
  • PHP 8.1: enums, readonly properties.

Ключевая тенденция — постепенное введение статических гарантий без отказа от динамики. Например, union types (string|int) позволяют описать вариативность, характерную для веб-данных, не теряя при этом проверки на этапе выполнения. Это отражает философию PHP: не навязывать парадигму, а предоставлять инструменты для выбора уровня контроля в зависимости от контекста задачи.


Модель исполнения: SAPI как интерфейс к миру

SAPI (Server Application Programming Interface) — это интерфейс между ядром PHP (Zend Engine) и внешним окружением. От выбора SAPI зависит не только способ запуска скрипта, но и его поведение при работе с потоками ввода-вывода, переменными окружения, жизненным циклом и даже доступностью некоторых функций.

PHP поставляется с несколькими встроенными SAPI, наиболее значимые из которых:

SAPIОписаниеОсобенности поведения
CLI (php script.php)Command-Line Interfaceargc, argv доступны глобально; — STDIN, STDOUT, STDERR — ресурсы; — header(), setcookie() вызывают ошибку (нет HTTP-контекста); — $_SERVER['argv'], $_SERVER['argc'], $_SERVER['PHP_SELF'] отличаются от веб-режима; — php.ini: cli/php.ini может отличаться от fpm/php.ini.
php-fpm (FastCGI Process Manager)Стандарт для Nginx/Apache (через proxy_pass или mod_proxy_fcgi)— Работает в режиме пула воркеров (долгоживущие процессы); — Поддерживает graceful restart, управление памятью, лимиты запросов; — Позволяет использовать OPcache эффективно (кэш сохраняется между запросами); — Каждый воркер обрабатывает запросы последовательно (не конкурентно внутри процесса).
Apache module (mod_php)Встраивается непосредственно в Apache— Устаревший, но исторически значимый режим; — Каждый дочерний процесс Apache загружает PHP целиком; — Проблемы с памятью при большом числе воркеров (PHP не выгружается между запросами); — Не поддерживает разные php.ini для разных виртуальных хостов.
Embedded (embed)Встраивание PHP в стороннее приложение (редко)— Позволяет использовать PHP как скриптовый движок внутри C/C++-приложения; — Используется, например, в некоторых CMS-системах с кастомным хостом.

Ключевое следствие: код, написанный без учёта SAPI, может вести себя по-разному. Например:

// В CLI:
echo $_SERVER['REQUEST_METHOD']; // Notice: Undefined index
// В php-fpm:
echo $_SERVER['REQUEST_METHOD']; // → GET

// В CLI:
echo $_SERVER['SCRIPT_NAME']; // → /path/to/script.php
// В веб-режиме:
echo $_SERVER['SCRIPT_NAME']; // → /index.php

Современная практика — явное определение SAPI через PHP_SAPI:

if (PHP_SAPI === 'cli') {
// CLI-логика
} elseif (in_array(PHP_SAPI, ['fpm-fcgi', 'apache2handler'])) {
// Веб-логика
}

Это не «костыль», а осознанная адаптация к контексту, заложенная в философию ядра: PHP не навязывает окружение — он реагирует на него.


Жизненный цикл скрипта: инициализация, выполнение, завершение

Каждый запуск PHP-скрипта проходит чётко определённые фазы, управляемые Zend Engine:

  1. MINIT (Module Initialization)
    Выполняется один раз при запуске SAPI (например, при старте php-fpm-воркера). Инициализируются расширения, глобальные структуры, OPcache.
    Не зависит от запроса.

  2. RINIT (Request Initialization)
    Выполняется перед каждым запросом.
    — Парсится php.ini, загружаются настройки (если не закэшированы);
    — Инициализируются суперглобальные массивы ($_GET, $_POST, $_SERVER и др.);
    — Выполняется auto_prepend_file (если указан);
    — Выделяется пул памяти для запроса (arena allocator).

  3. Execution
    Интерпретируется OPcode (либо компилируется JIT в PHP 8+).
    — Выполняются include, require, определения классов/функций;
    — Создаются и уничтожаются локальные переменные в стеке вызовов;
    — Могут срабатывать автозагрузчики (spl_autoload_register).

  4. RSHUTDOWN (Request Shutdown)
    После генерации ответа (или аварийного завершения):
    — Выполняется auto_append_file;
    — Завершаются сессии (session_write_close при session.auto_start);
    — Освобождается память запроса;
    — Сбрасываются статические переменные внутри функций и методов.

  5. MSHUTDOWN (Module Shutdown)
    При завершении процесса (например, kill -TERM для fpm-воркера).
    — Выгружаются расширения;
    — Освобождается разделяемая память (включая OPcache).

Этот цикл объясняет, почему статические переменные в функциях не сохраняются между запросами в традиционной модели, но могут сохраняться в долгоживущих SAPI (например, при использовании Swoole). Это также определяет границы действия register_shutdown_function() — он вызывается в RSHUTDOWN, а не после него.


Обработка ошибок и исключений: иерархия реакций

В PHP реализована двуслойная система обработки аномалий:

1. Ошибки (errors) — на уровне исполнения

Генерируются ядром или расширениями при нарушении предусловий выполнения (например, деление на ноль, обращение к несуществующему индексу массива, вызов несуществующей функции). До PHP 7 ошибки не были исключениями и не перехватывались try/catch.

Начиная с PHP 7, большинство фатальных и recoverable ошибок преобразуются в Error (и его подклассы: TypeError, ParseError, ArgumentCountError), которые наследуют от Throwable, как и Exception. Это означает:

try {
$arr = [];
echo $arr['missing']; // PHP Notice → в PHP 7+ не бросает исключение
undefined_function(); // → Error (Fatal Error)
} catch (Error $e) {
// Перехват фатальной ошибки как исключения
}

Однако notices и warnings по-прежнему не являются исключениями. Их можно перехватить только через set_error_handler():

set_error_handler(function($errno, $errstr, $errfile, $errline) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});

Такой подход — компромисс: сохранение совместимости (старый код с E_NOTICE продолжает работать), но предоставление пути к строгой обработке.

2. Исключения (exceptions) — на уровне логики

Явно выбрасываются через throw, перехватываются через try/catch. В PHP 8.0 появилась поддержка catch без переменной:

try {
$value = $nullable ?? throw new \InvalidArgumentException();
} catch (\InvalidArgumentException) {
// Переменная не нужна — только логирование/переход
}

А в PHP 8.4 (на момент 2025 г. — в стадии финального релиза) появится Error как строгий тип по умолчанию для необработанных исключений, что усилит гарантии.

Важно: Error и Exception — две ветви одной иерархии (Throwable), но они имеют разное происхождение и семантику:

  • Exceptionлогическое нарушение (невалидные данные, бизнес-ошибка);
  • Errorсистемное нарушение (ошибка программиста, нарушение контракта языка).

Разделение позволяет строить многоуровневую стратегию обработки: например, TypeError можно логировать как баг, а ValidationException — как часть нормального потока.


Безопасность «из коробки»: что делает ядро, а что — разработчик

PHP не обеспечивает безопасность автоматически, но предоставляет механизмы, позволяющие её построить.

1. Регистр переменных (magic_quotes, register_globals)

Устаревшие механизмы, полностью удалённые в PHP 5.4+, служат напоминанием: попытки «автоматической» защиты (например, экранирование кавычек в $_GET) лишь создавали ложное чувство безопасности и усложняли отладку. Современный PHP не модифицирует входные данные.

2. Сессии

— Генерация session.id по умолчанию использует CSPRNG (с PHP 7.1);
— Поддержка строгого режима (session.use_strict_mode = 1) — запрет использования неизвестных ID;
— Возможность привязки сессии к IP/User-Agent (хотя это спорная практика);
— Встроенные обработчики хранения: files, redis, memcached, pdo.

Но: PHP не шифрует данные сессии на стороне сервера и не защищает от фиксации ID (session fixation) без дополнительных мер.

3. Фильтрация и валидация

Расширение filter (filter_var(), filter_input()) предоставляет встроенные механизмы:

$email = filter_input(INPUT_GET, 'email', FILTER_VALIDATE_EMAIL);
$int = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 1000]
]);

Однако FILTER_SANITIZE_STRING удалён в PHP 8.1 как потенциально опасный (слишком оптимистичная санитизация). Корректный подход — валидация + экранирование на выходе (например, htmlspecialchars() для HTML).

4. Открытый доступ к файловой системе

Функции include, require, file_get_contents, fopen работают с абсолютными и относительными путями, включая http://, php://, data://. Это мощно, но опасно:

include $_GET['page'] . '.php'; // LFI-уязвимость

Ограничения:

  • open_basedir — ограничение доступа к файлам за пределами указанных директорий;
  • allow_url_fopen, allow_url_include — управление включением удалённых ресурсов.

Но эти директивы не включены по умолчанию. Безопасность — в архитектуре приложения.


Расширяемость: как PHP становится «своим»

PHP поддерживает расширяемость на трёх уровнях:

1. Функции и классы пользовательского уровня

function, class, trait, interface — основа повторного использования;
spl_autoload_register() — динамическая загрузка классов;
— Пространства имён (namespace) — изоляция имён.

2. Расширения на C (zend_extension, extension)

Компилируемые модули, работающие на уровне Zend Engine:

  • opcache.so — кэширование OPcode;
  • xdebug.so — отладка и профилирование;
  • redis.so, mongodb.so — драйверы БД.

Разработка расширений требует знания C и API Zend Engine, но даёт максимальную производительность.

3. Stream wrappers и stream filters

Механизм, позволяющий определить собственное поведение для URL-подобных строк:

stream_wrapper_register('myproto', MyProtoStream::class);
file_get_contents('myproto://resource'); // вызовет MyProtoStream::stream_open()

Аналогично, stream_filter_register() позволяет встраивать трансформации (например, zlib.deflate, convert.iconv.*).

Это делает PHP языком-платформой: он не просто исполняет код — он предоставляет интерфейсы для построения собственных сред внутри себя.


Архитектурные паттерны: от «спагетти» к слоям

Хотя язык не навязывает архитектуру, в экосистеме PHP сложились устойчивые модели:

ПаттернХарактеристикиПримеры
Процедурный (mixed)HTML + PHP в одном файле, глобальные переменные, include как композицияРанний WordPress, «классические» PHP-сайты 2000-х
Front ControllerОдин точка входа (index.php), маршрутизация через $_SERVER['REQUEST_URI']CodeIgniter (частично), ручная реализация
MVC (Model-View-Controller)Чёткое разделение: логика → модели, представление → шаблоны, связка → контроллерыLaravel, Symfony, CakePHP
Middleware (PSR-15)Последовательная обработка запроса через цепочку делегатовSlim, Zend Stratigility, Laminas Mezzio
Domain-Driven Design (DDD)Слой домена → прикладной слой → инфраструктурный слойSylius, Laravel + packages (Laravel Actions, Spatie DataTransferObject)

Ключевая эволюция — от глобального состояния к инверсии управления:

  • Ранний PHP: $db = new PDO(...); в каждом файле;
  • Современный PHP: ContainerInterface, Dependency Injection, Service Locator (но DI предпочтительнее).

Стандарты PSR (PHP Standard Recommendations) сыграли решающую роль:

  • PSR-4 — автозагрузка;
  • PSR-7 — HTTP-сообщения (Request, Response);
  • PSR-15 — middleware;
  • PSR-18 — HTTP-клиент.

Они не являются частью языка, но формируют среду, в которой PHP используется как компонент крупной системы, а не как «скрипт в корне сайта».


Совместимость и управление версиями: от монолита к композиту

Исторически PHP развивался в режиме монолитной совместимости: каждая новая версия стремилась к обратной совместимости на уровне синтаксиса и поведения функций. Однако с ростом сложности языка и экосистемы (особенно после перехода на Composer и компонентную архитектуру) эта парадигма уступила место управляемой несовместимости.

1. Семантическое версионирование в экосистеме

Сам PHP не следует SemVer — его версии (8.1, 8.2, 8.3, 8.4) отражают мажорные релизы с новыми возможностями и потенциальными BC-разрывами. Однако пакеты, устанавливаемые через Composer, строго придерживаются SemVer 2.0:

  • 1.2.3MAJOR.MINOR.PATCH;
  • ^1.2.0 — совместимость по API: допускает 1.3.0, но не 2.0.0;
  • ~1.2.0 — совместимость по патчам и минорным версиям в пределах 1.2.x.

Критически важна директива platform в composer.json:

{
"config": {
"platform": {
"php": "8.1.0"
}
}
}

Она фиксирует целевую версию PHP при разрешении зависимостей, предотвращая установку пакетов, требующих ext-redis:^6.0 на сервере с ext-redis:5.3.7. Это позволяет разрабатывать на PHP 8.4, но деплоить на PHP 8.1 — без риска подтянуть неподдерживаемые зависимости.

2. Управление расширениями

PHP поставляется в виде ядра и опциональных расширений (ext-*). Состав расширений — не часть языка, а свойство окружения. Это создаёт проблему переносимости: код, использующий imagick, не запустится без ext-imagick.

Composer позволяет декларировать зависимости от расширений:

{
"require": {
"ext-pdo": "*",
"ext-json": "*",
"ext-redis": "^5.3.0"
}
}

При composer install будет проверена доступность расширений — и ошибка выдана до запуска кода. Это переносит проверку из runtime в этап сборки, повышая надёжность.

Важно: PHP не имеет «стандартной библиотеки» в стиле Python или Java. Даже json_encode() — часть расширения ext-json, которое может быть отключено. В production-сборках рекомендуется явно фиксировать список необходимых расширений.


Взаимодействие с внешними системами: границы исполнения

PHP — не изолированный исполнитель; он встраивается в экосистему ОС и сетевых сервисов. Ядро предоставляет три основных механизма интеграции:

1. Системные вызовы и процессы

Функции exec(), shell_exec(), system(), passthru(), proc_open() позволяют запускать внешние программы. Ключевые отличия:

ФункцияВозвратПотокиБезопасность
exec($cmd, &$output, &$code)Последняя строка stdoutstdout → массив $outputТребует экранирования (escapeshellarg())
shell_exec($cmd)Полный stdout как строкаstderr смешивается с stdoutАналогично
proc_open($cmd, $descriptors, &$pipes)Ресурс процессаПолный контроль: stdin/stdout/stderr как потокиВозможен non-blocking I/O

Пример безопасного вызова:

$filename = escapeshellarg($_GET['file']);
$output = shell_exec("cat $filename 2>&1");

Без escapeshellarg() — RCE-уязвимость.

2. Foreign Function Interface (FFI)

Начиная с PHP 7.4, в ядро интегрирован FFI — механизм прямого вызова функций из разделяемых библиотек (*.so, *.dll) без написания C-расширения:

$libc = FFI::cdef("
int printf(const char *format, ...);
", "libc.so.6");

$libc->printf("Hello %s!\n", "FFI"); // → Hello FFI!

FFI — не «мост к C», а интерфейс к двоичному ABI. Он требует знания структур данных и соглашений вызова (cdecl, stdcall), но позволяет:

  • Интегрировать legacy-библиотеки (например, libz для сжатия);
  • Писать высокопроизводительные примитивы (например, хэш-функции);
  • Встраивать движки (Lua, V8) без посредника.

Ограничение: FFI отключён по умолчанию в php.ini (ffi.enable = preload или true), так как нарушает sandboxing.

3. Потоки и обёртки (streams)

PHP-потоки (php://, http://, data://, compress.zlib://) — единый интерфейс для работы с любыми источниками данных, абстрагирующий протокол и транспорт:

// Чтение из stdin
$input = file_get_contents('php://stdin');

// Отправка в сжатый буфер
file_put_contents('compress.zlib://output.gz', $data);

// Создание временного файла в памяти
$temp = fopen('php://temp/maxmemory:1048576', 'w+');

Каждая обёртка реализует интерфейс stream_wrapper:

  • stream_open(), stream_read(), stream_write(), stream_close() — базовые операции;
  • stream_stat(), url_stat() — метаданные;
  • stream_cast() — получение нативного дескриптора.

Это позволяет писать, например, s3://bucket/key как обычный файл — при наличии aws/aws-sdk-php с поддержкой stream wrapper’а.


Модель распределённого состояния: сессии, кэш, очередь

В веб-среде состояние между запросами не сохраняется. PHP предоставляет механизмы для явного управления распределённым состоянием — но не реализует их «из коробки», а лишь задаёт интерфейсы.

1. Сессии: контракт, а не реализация

Функции session_start(), $_SESSION, session_regenerate_id() работают через обработчик сессий (session.save_handler). Ядро включает:

  • files — хранение в /tmp/sess_* (по умолчанию);
  • user — кастомный обработчик через session_set_save_handler().

Современные приложения почти всегда заменяют files на:

  • redis (через ext-redis);
  • memcached;
  • pdo (собственный DSN).

Ключевые параметры php.ini:

session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379?database=2"
session.gc_maxlifetime = 1440 ; время жизни в секундах
session.cookie_secure = 1 ; только по HTTPS
session.cookie_httponly = 1 ; недоступна из JS

Сессии — не место для хранения данных. Они предназначены для идентификации и аутентификации. Данные должны храниться в БД, а в сессии — только ID.

2. Кэширование: от APCu к PSR-6

  • APCu (apc.enable_cli=1 при необходимости) — локальный in-memory кэш для одного процесса (не shared между воркерами!);
  • Redis/Memcached — распределённый кэш, совместимый с PSR-6 (CacheItemPoolInterface);
  • OPcache — кэш кода, а не данных.

Важно различать:

  • opcache_reset() — сброс OPcache (требует opcache.restrict_api);
  • apcu_clear_cache() — сброс APCu;
  • $redis->flushdb() — сброс Redis.

3. Асинхронность и очереди

PHP — однопоточный, но поддерживает асинхронные паттерны:

  • CLI-очереди (Laravel Queues, Symfony Messenger):

    php artisan queue:work --daemon

    Задачи сериализуются в Redis/RabbitMQ/DB, обрабатываются воркерами.

  • Non-blocking I/O (через stream_select(), ReactPHP, Amp):

    $socket = stream_socket_server("tcp://127.0.0.1:8080");
    stream_set_blocking($socket, false);
    $read = [$socket];
    while (true) {
    $changed = $read;
    stream_select($changed, $write, $except, null);
    // обработка событий
    }
  • Корутины (Swoole, RoadRunner):

    Co\run(function () {
    $redis = new Co\Redis;
    $redis->connect('127.0.0.1', 6379);
    $val = $redis->get('key');
    });

    Переключение контекста происходит на yield-точках (I/O), а не на потоках.


Эволюция стандартов: от PEAR к PSR и Composer

PHP прошёл путь от централизованного репозитория (PEAR) к децентрализованной экосистеме (Packagist + Composer), управляемой сообществом через PHP-FIG (Framework Interop Group).

ЭтапИнструментПроблемаРешение
До 2009Ручное копирование файлов, include_pathКонфликты имён, отсутствие зависимостей
2009–2012PEAR, PECLЦентрализация, монолитные пакеты, отсутствие versioning
2012–2014Composer, PackagistФрагментация, несовместимостьPSR-0 → PSR-4 (автозагрузка)
2015–настоящееPSR-7 (HTTP), PSR-15 (Middleware), PSR-18 (HTTP Client)Нестандартизированные контрактыИнтерфейсы вместо реализаций

Ключевой результат — интероперабельность:

  • Вы можете использовать GuzzleHttp\Client (PSR-18) с Symfony HttpClient через адаптер;
  • Middleware от Slim работает в Laravel через laravel/psr15-bridge;
  • Логгер Monolog (Psr\Log\LoggerInterface) интегрируется в любой фреймворк.

Это превратило PHP из «языка с фреймворками» в платформу с компонентами.