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

Модель исполнения PHP

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

Модель исполнения PHP

LAMP (Linux, Apache, MySQL, PHP)

LAMP представляет собой набор программного обеспечения для создания и хостинга веб-сайтов. Аббревиатура расшифровывается по компонентам стека.

  • Linux — операционная система.
  • Apache — веб-сервер.
  • MySQL — система управления базами данных.
  • PHP — язык программирования.

Все компоненты обладают открытым исходным кодом. Это снижает стоимость развертывания проекта. Сообщество обеспечивает поддержку и безопасность компонентов.

Существует вариация стека под названием LEMP. Заменяется веб-сервер Apache на Nginx. Набор ПО включает Linux, Nginx, MySQL, PHP. Nginx показывает высокую производительность при больших нагрузках.

Популярность в области построения веб-сайтов определяется наличием большого набора встроенных средств и дополнительных модулей. Стек обеспечивает полный цикл работы веб-приложения. Операционная система управляет ресурсами. Веб-сервер обрабатывает запросы. База данных хранит информацию. Язык программирования реализует логику.


Системы управления сайтами (CMS) и фреймворки (CMF)

Экосистема PHP включает готовые решения для ускорения разработки. Системы управления сайтами (CMS) позволяют создавать сайты без написания кода с нуля. Фреймворки предоставляют библиотеки и структуру для разработки уникальных проектов.

Популярные CMS на PHP:

  • WordPress — платформа для блогов и сайтов визиток.
  • Joomla — универсальная система для порталов.
  • Drupal — решение для сложных проектов и государственных сайтов.
  • 1C-Битрикс — коммерческая система для бизнеса.

Популярные фреймворки на PHP:

  • Laravel — современный фреймворк с выразительным синтаксисом.
  • Symfony — набор компонентов для корпоративных приложений.
  • Yii — высокопроизводительный фреймворк.
  • CodeIgniter — легковесное решение.

Использование готовых систем сокращает время разработки. Безопасность и функциональность поддерживаются сообществом. Разработчик фокусируется на уникальных фичах проекта.

Среди крупных проектов на PHP — Wikipedia (MediaWiki), WordPress (доля сайтов в интернете), коммерческие CMS и банковские системы на Symfony/Laravel. Масштабные платформы часто комбинируют PHP с кэшем, очередями и микросервисами на других языках.

PHP остаётся одним из самых распространённых языков веб-бэкенда — огромная база legacy-кода, низкий порог входа, зрелая экосистема Composer и фреймворков. Точное место в рейтингах (TIOBE, GitHub, опросы Stack Overflow) меняется ежегодно — для цифр смотрите актуальные отчёты, а не фиксированную дату в учебнике.


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">. Валидация MIME и хранение — Загрузка файлов и валидация в PHP.
$_REQUESTОбъединение $_GET, $_POST, $_COOKIE (в порядке, задаваемом variables_order)Из-за неопределённости источника не рекомендуется к использованию в production-коде.
$_ENVПеременные окружения процессаДоступны только при включённой директиве variables_order, включающей E.

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


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

Синтаксис 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 (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

Разбор:

  • $_SERVER — суперглобальный массив с параметрами окружения запроса и сервера.
  • REQUEST_METHOD в веб-контексте содержит HTTP-метод (GET, POST, PUT), а в CLI этого ключа нет, поэтому появляется notice.
  • SCRIPT_NAME в CLI показывает путь до запускаемого файла, а в веб-режиме — путь скрипта относительно web-root (часто /index.php).
  • Фрагмент демонстрирует, что один и тот же PHP-код ведёт себя по-разному в зависимости от SAPI.
  • Практический вывод — код, который работает и в вебе, и в консоли, должен проверять наличие ключей через isset() или оператор null coalescing.

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

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

Разбор:

  • PHP_SAPI — встроенная константа PHP, возвращающая текущий интерфейс выполнения (cli, fpm-fcgi, apache2handler и другие).
  • Первый if отсекает ветку для консольных сценариев: здесь обычно работают с аргументами командной строки, stdout/stderr и cron-логикой.
  • in_array(...) проверяет принадлежность текущего SAPI к списку веб-режимов.
  • Разделение веток в одном месте упрощает поддержку: вы явно фиксируете контракты окружения для каждой части кода.
  • Такой шаблон полезен в bootstrap-скриптах, где нужно выбрать источник входных данных, формат ошибок и способ формирования ответа.

Это осознанная адаптация к контексту, заложенная в философию ядра: 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.


Разбор запроса на практике

Пример типичной цепочки для приложения на Laravel или Symfony:

  1. Браузер запрашивает /profile.
  2. Nginx/Apache передаёт запрос в php-fpm.
  3. Запускается public/index.php и автозагрузка vendor/autoload.php.
  4. Фреймворк строит Request, запускает middleware и контроллер.
  5. Возвращается Response со статусом и телом.
  6. PHP завершает запрос и освобождает память.

Именно поэтому ошибки в bootstrap-файлах, автозагрузке или middleware проявляются "до бизнес-логики".


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

В 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) {
// Перехват фатальной ошибки как исключения
}

Разбор:

  • try { ... } catch (...) формирует блок обработки исключительных ситуаций.
  • echo $arr['missing'] создаёт notice о несуществующем ключе массива, но не генерирует Throwable по умолчанию.
  • undefined_function() вызывает фатальную ошибку времени выполнения, которая в PHP 7+ представляется объектом Error.
  • catch (Error $e) перехватывает ошибки класса Error и его наследников (TypeError, ParseError и другие), но не перехватывает Exception, если ловите только Error.
  • Фрагмент наглядно разделяет два класса проблем: "мягкие" предупреждения и "жёсткие" ошибки исполнения.

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

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

Разбор:

  • set_error_handler(...) регистрирует пользовательский обработчик PHP-ошибок уровня warning/notice/deprecated.
  • Анонимная функция принимает код ошибки, текст, файл и строку — это контекст события.
  • throw new ErrorException(...) конвертирует стандартные PHP-ошибки в исключения, пригодные для try/catch.
  • Такой подход делает поток ошибок единообразным: логика обработки переносится в один механизм исключений.
  • В продакшене обычно добавляют фильтрацию по error_reporting(), чтобы не эскалировать подавленные ошибки.

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


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

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

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

Разбор:

  • Оператор ?? (null coalescing) возвращает левое значение, если оно не null.
  • Конструкция throw внутри выражения позволяет сразу прервать выполнение при null.
  • \InvalidArgumentException указывает на ошибку входных данных, а обратный слеш означает глобальное пространство имён.
  • catch (\InvalidArgumentException) без переменной подходит, когда объект исключения не нужен в коде обработчика.
  • Фрагмент показывает лаконичный стиль guard-проверок для обязательных параметров.

А в 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_input(INPUT_GET, ...) читает и валидирует конкретный параметр из query string за одну операцию.
  • FILTER_VALIDATE_EMAIL проверяет синтаксис email и возвращает строку либо false.
  • FILTER_VALIDATE_INT валидирует целое число.
  • Блок options задаёт дополнительные границы (min_range, max_range), что защищает от выхода за допустимый диапазон.
  • Итоговые переменные требуют явной проверки (!== false), потому что невалидный ввод возвращает булево false.

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


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

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

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

Разбор:

  • include исполняет внешний PHP-файл в текущем контексте.
  • $_GET['page'] берётся напрямую из пользовательского ввода без валидации.
  • Конкатенация строки '.php' не защищает от обхода путей (../) и других техник LFI.
  • В случае уязвимости злоумышленник может подключить неожиданный локальный файл и раскрыть/выполнить код.
  • Безопасный подход: белый список разрешённых шаблонов или маппинг маршрутов через фиксированную таблицу.

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

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

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


Расширяемость

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_wrapper_register(...) регистрирует пользовательский протокол для stream API.
  • 'myproto' — имя схемы, которую затем можно использовать как URL (myproto://...).
  • MyProtoStream::class передаёт имя класса-обработчика, реализующего методы stream wrapper.
  • file_get_contents(...) вызывает не файловую систему напрямую, а ваш обработчик stream_open() и связанные методы чтения.
  • Механизм позволяет унифицировать доступ к нестандартным источникам данных через привычные файловые функции PHP.

Аналогично, 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 Проектирование (DDD)Слой домена → прикладной слой → инфраструктурный слойSylius, Laravel + packages (Laravel Actions, Spatie DataTransferObject)

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

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

Разбор:

  • Это маркер архитектурного паттерна Service Locator, а не исполняемая shell-команда.
  • Паттерн централизует получение зависимостей через общий контейнер/реестр сервисов.
  • Преимущество — быстрый доступ к сервисам из разных частей приложения.
  • Ограничение — неявные зависимости: по сигнатуре класса не всегда видно, что именно ему нужно.
  • В современных PHP-проектах чаще предпочитают явный Dependency Injection, особенно для тестируемости.

Стандарты 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"
}
}
}

Разбор:

  • JSON-фрагмент показывает секцию config.platform в composer.json.
  • Поле "php": "8.1.0" фиксирует целевую версию PHP для расчёта совместимых зависимостей.
  • Composer начинает решать граф пакетов так, будто проект работает именно на указанной версии.
  • Это стабилизирует сборку между разработческой машиной и продакшеном с другой фактической версией интерпретатора.
  • Практически полезно в CI, чтобы предотвратить случайную установку пакетов, требующих более новый runtime. Она фиксирует целевую версию 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"
}
}

Разбор:

  • Блок require может включать не только пакеты, но и PHP-расширения через префикс ext-.
  • "*" означает требование наличия расширения без жёсткой фиксации версии.
  • "ext-redis": "^5.3.0" задаёт допустимый диапазон версий расширения по semver.
  • При composer install проверка расширений выполняется до запуска приложения, что снижает риск runtime-сбоев.
  • Такой контракт полезен для повторяемого деплоя и документации требований окружения. При 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");

Разбор:

  • $_GET['file'] приходит от пользователя и потенциально содержит специальные символы shell.
  • escapeshellarg(...) оборачивает значение в безопасный аргумент, экранируя метасимволы.
  • shell_exec(...) запускает команду оболочки и возвращает stdout как строку.
  • 2>&1 перенаправляет stderr в stdout, чтобы в результате видеть и ошибки выполнения.
  • Ключевой смысл примера: внешние команды допустимы только с обязательной нормализацией и экранированием аргументов. Без 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::cdef(...) объявляет C-сигнатуры функций, доступных из динамической библиотеки.
  • Второй аргумент "libc.so.6" указывает целевую системную библиотеку.
  • Объявление int printf(const char *format, ...); сообщает PHP формат и типы вызова C-функции.
  • После связывания $libc->printf(...) выполняет нативный вызов напрямую из PHP-кода.
  • Пример показывает мощь FFI и одновременно риск: доступ к нативному ABI требует осторожного контроля окружения и безопасности.

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

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

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


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

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

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

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

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

Разбор:

  • php://stdin даёт доступ к стандартному входному потоку процесса (актуально для CLI и пайпов).
  • compress.zlib://... — stream wrapper, автоматически применяющий сжатие при записи.
  • file_put_contents(...) в этом случае работает через обёртку протокола, а не обычный файловый writer.
  • php://temp/maxmemory:1048576 создаёт временный поток: сначала в памяти, после лимита — с fallback на диск.
  • Пример показывает унифицированный интерфейс streams: одинаковые функции работают с разными источниками/приёмниками данных.

Каждая обёртка реализует интерфейс 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

Разбор:

  • session.save_handler = redis переключает backend хранения сессий на Redis.
  • session.save_path задаёт адрес Redis и дополнительные параметры подключения (здесь база 2).
  • session.gc_maxlifetime определяет TTL сессионных данных на уровне PHP-механизма сборки мусора.
  • session.cookie_secure = 1 разрешает отправку cookie только по HTTPS.
  • session.cookie_httponly = 1 блокирует доступ к cookie из JavaScript и снижает риски при XSS.

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


2. Кэширование

  • 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

Разбор:

  • php artisan запускает консольный интерфейс Laravel.

  • Команда queue:work поднимает воркер очередей и начинает обработку задач из backend-хранилища.

  • Флаг --daemon переводит воркер в долгоживущий режим процесса.

  • Такой запуск снижает накладные расходы на инициализацию фреймворка между задачами.

  • Для продакшена обычно добавляют внешний process manager (supervisor, systemd) и лимиты перезапуска. Задачи сериализуются в 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);
// обработка событий
}

Разбор:

  • stream_socket_server(...) поднимает TCP-сервер, слушающий локальный порт 8080.

  • stream_set_blocking(..., false) включает неблокирующий режим сокета.

  • Массив $read хранит дескрипторы, за которыми нужно наблюдать.

  • stream_select(...) ожидает готовность сокетов к чтению и реализует событийный цикл.

  • while (true) формирует основу event loop, где обработка запросов происходит по событиям ввода-вывода.

  • Корутины (Swoole, RoadRunner):

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

Разбор:

  • Co\run(...) запускает корутинный runtime (например, Swoole) для асинхронного выполнения.
  • Анонимная функция становится корутинным контекстом, в котором I/O-операции могут отдавать управление scheduler.
  • new Co\Redis создаёт корутинный клиент Redis.
  • connect(...) и get(...) выглядят синхронно в коде, но внутри выполняются неблокирующе.
  • Такой стиль упрощает написание конкурентного кода без явных callback-цепочек. Переключение контекста происходит на yield-точках (I/O), а не на потоках.

Чеклист понимания темы

  • Я понимаю разницу между cli, fpm-fcgi и apache2handler.
  • Я понимаю, почему состояние запроса в PHP по умолчанию не сохраняется между HTTP-вызовами.
  • Я знаю, где использовать сессии, а где хранить данные в БД или кэше.
  • Я понимаю, почему composer.lock и автозагрузка влияют на поведение приложения в рантайме.

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


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.

Содержание