Модель исполнения 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 предоставляет эти данные в виде предопределённых массивов, доступных в любой точке скрипта без объявления.
| Массив | Источник данных | Особенности |
|---|---|---|
$_GET | Query 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 Interface | — argc, 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:
-
MINIT (Module Initialization)
Выполняется один раз при запуске SAPI (например, при старте php-fpm-воркера). Инициализируются расширения, глобальные структуры, OPcache.
Не зависит от запроса. -
RINIT (Request Initialization)
Выполняется перед каждым запросом.
— Парситсяphp.ini, загружаются настройки (если не закэшированы);
— Инициализируются суперглобальные массивы ($_GET,$_POST,$_SERVERи др.);
— Выполняетсяauto_prepend_file(если указан);
— Выделяется пул памяти для запроса (arena allocator). -
Execution
Интерпретируется OPcode (либо компилируется JIT в PHP 8+).
— Выполняютсяinclude,require, определения классов/функций;
— Создаются и уничтожаются локальные переменные в стеке вызовов;
— Могут срабатывать автозагрузчики (spl_autoload_register). -
RSHUTDOWN (Request Shutdown)
После генерации ответа (или аварийного завершения):
— Выполняетсяauto_append_file;
— Завершаются сессии (session_write_closeприsession.auto_start);
— Освобождается память запроса;
— Сбрасываются статические переменные внутри функций и методов. -
MSHUTDOWN (Module Shutdown)
При завершении процесса (например,kill -TERMдля fpm-воркера).
— Выгружаются расширения;
— Освобождается разделяемая память (включая OPcache).
Этот цикл объясняет, почему статические переменные в функциях не сохраняются между запросами в традиционной модели, но могут сохраняться в долгоживущих SAPI (например, при использовании Swoole). Это также определяет границы действия register_shutdown_function() — он вызывается в RSHUTDOWN.
Разбор запроса на практике
Пример типичной цепочки для приложения на Laravel или Symfony:
- Браузер запрашивает
/profile. - Nginx/Apache передаёт запрос в
php-fpm. - Запускается
public/index.phpи автозагрузкаvendor/autoload.php. - Фреймворк строит
Request, запускает middleware и контроллер. - Возвращается
Responseсо статусом и телом. - 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.3→MAJOR.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) | Последняя строка stdout | stdout → массив $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и автозагрузка влияют на поведение приложения в рантайме.
Связанные статьи
- Composer — управление зависимостями в PHP
- Фреймворки и библиотеки PHP
- Синтаксис, операторы и пунктуация в PHP
- Встроенные функции и расширения PHP
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.