Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Streams: потоки, контексты, фильтры и свои протоколы
  2. Сначала результат: экспорт в памяти без отдельного файла
  3. Wrapper, transport, context и filter: разные роли
  4. Что установлено и какие возможности проверять
  5. Ресурсы, настоящие классы и прототипы callback
  6. Контекст: отдельная политика каждого обращения
  7. Готовые фильтры: направление, порядок и финальный хвост
  8. Практика: нормализовать файл артикулов и сохранить результат
  9. Свой фильтр: кто вызывает методы и кому принадлежат данные
  10. StreamBucket и brigade: блок, очередь и учёт байтов
  11. Собственный протокол: когда wrapper оправдан
  12. Полный учебный wrapper: путь, запись, позиция и чтение
  13. Отказ, пустой результат, timeout и безопасная диагностика
  14. Из одного интерфейса — память, файл и сжатый экспорт
  15. Полный набор констант: флаги, режимы, события и статусы
  16. Практика: ограниченный импорт и проверка граничных случаев
  17. Все функции и callbacks семейства Streams
  18. Что ломалось в реальных задачах и как это воспроизвести
  19. Функции и методы этой темы
  20. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 109 · От первого потока к своему протоколу

Streams: потоки, контексты, фильтры и свои протоколы

Полная категория Streams: от ресурса в памяти до сетевых состояний, очередей buckets и 25 callbacks пользовательского wrapper.

Сначала результат: экспорт в памяти без отдельного файла

Наглядная модель

Одна строка, ресурс и его позиция

Сначала сохраняем tea в памяти, затем читаем те же байты. Имя ресурса не является его содержимым.

  1. $stream → пустой канал

    fopen создаёт php://memory. Позиция 0, байтов ещё нет.

  2. fwrite: t e a |

    Сохранены три байта. Вертикальная черта показывает позицию после a.

  3. rewind: | t e a

    Байты остались на месте; позиция вернулась перед t.

  4. $text = tea

    stream_get_contents прочитал остаток и вернул отдельную строку.

  5. fclose освобождает ресурс

    Строка $text остаётся доступна, а читать закрытый $stream уже нельзя.

Позиция измеряется в байтах. Эта модель подходит перематываемому memory-потоку; сеть не обязана поддерживать rewind.

Нужно подготовить маленький экспорт, а библиотека принимает открытый файл. Создавать настоящий файл для трёх байтов необязательно: PHP может открыть поток в памяти. Сначала запишем короткий артикул tea, вернём позицию к началу и прочитаем результат. Это механика одного открытого ресурса; затем применим её к файлу выгрузки ниже. Сначала увидим полезное действие; устройство фильтров, сети и своих протоколов понадобится значительно позже.

Где лежит tea и куда смотрит позиция
  1. fopen открывает пустой php://memory
  2. fwrite сохраняет байты tea; позиция после них
  3. rewind ставит позицию перед t
  4. stream_get_contents возвращает tea в PHP
  5. fclose освобождает ресурс
Записать и прочитать маленький экспорт в памятиСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://memory', 'w+');
fwrite($stream, 'tea');
rewind($stream);
$text = stream_get_contents($stream);
echo $text, "\n";
fclose($stream);
Ожидаемый результат
tea

Это первый опыт с известным встроенным memory-потоком и крошечным вводом. $stream хранит ресурс — открытый канал, $text уже строку данных. После write позиция стоит в конце; без rewind оставшаяся часть была бы пустой. Ни файл сайта, ни внешний сервер не изменяются.

ЗаписьПонятный смысл
$stream = fopen(...)Скобки вызывают функцию, = сохраняет возвращённый ресурс. В общем случае открытие может вернуть false.
'php://memory'Строка адреса: php выбирает wrapper, memory — его особый target.
'w+'Открыть для чтения и записи; это режим работы, а не тип данных.
fwrite / stream_get_contentsПервая функция принимает байты, вторая возвращает оставшиеся байты строкой.
rewind / fcloseПереместить позицию в начало / закрыть ресурс. Эти действия не заменяют друг друга.

Далее понадобятся вызов, аргументы и возвращаемое значение, начальные опыты с потоками и обработка ошибок. Поток — не параллельный thread и не Generator: здесь речь о последовательном чтении/записи байтов. Возможность произвольной перемотки есть не у каждого источника.

Wrapper, transport, context и filter: разные роли

Общий интерфейс скрывает способ получения байтов. file:// обращается к файлу, http:// использует HTTP, compress.zlib:// работает со сжатым содержимым, php:// даёт специальные каналы PHP. Если scheme не указана, обычно выбирается file. target для файла означает путь, для сети — адрес с требованиями конкретного протокола; нельзя трактовать их одинаково.

Три независимых слоя
  1. Адрес выбирает wrapper: где взять/куда сохранить
  2. Context задаёт параметры этого обращения
  3. Filter меняет проходящие байты
  4. Приложение читает/пишет через общий stream resource
СлойПример / чего не делает
Wrapperfile/http/свой lesson: реализует доступ к источнику, не обязательно шифрует.
Transporttcp/udp/tls: механизм соединения под сетевым источником; список отличается от wrappers.
Contexthttp.timeout или ssl.peer_name: политика обращения, не само выполнение запроса.
FilterBase64/сжатие/свой преобразователь: стадия байтов, не открытие другого адреса.
PHP/SAPI output bufferob_start собирает печать; это отдельная система от позиции и буфера открытого файла.

Старая вводная страница иллюстрирует HTTP/1.0. Современный HTTP wrapper по умолчанию использует HTTP/1.1 с PHP 8.0; это версия протокола конкретного wrapper, не вечное определение stream. Точные URL-options, поддерживаемые протоколы и безопасность внешних URL имеют свои главы официального справочника. В этом разделе не выдаём перечень stream API за полный HTTP-клиент или полноценный event loop.

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

У Streams нет отдельного PECL-пакета, который нужно поставить перед fopen: основная инфраструктура поставляется с PHP. Но возможности конкретного источника зависят от сборки, расширений и ОС: TLS требует подходящей криптографической поддержки, zlib — соответствующего расширения, Unix sockets не поддержаны этой Windows-лабораторией. Проверяйте возможности, а не название версии само по себе.

Проверить доступность нужных возможностей без публикации phpinfoСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(function_exists('stream_get_contents'));
var_dump(in_array('php', stream_get_wrappers(), true));
var_dump(in_array('tcp', stream_get_transports(), true));
var_dump(in_array('string.toupper', stream_get_filters(), true));
Ожидаемый результат
bool(true)
bool(true)
bool(true)
bool(true)

Проверены четыре конкретные возможности окружения примеров. Полный реестр может меняться при загрузке расширений и пользовательских регистрациях; не фиксируйте один вывод всех имён как обязательный для любой установки.

Влияющие общие настройки находятся в других категориях: allow_url_fopen ограничивает соответствующие URL-wrappers, allow_url_include отдельно относится к include URL и deprecated с PHP 7.4, default_socket_timeout задаёт стандартное сетевое ожидание. Не отключайте проверку TLS ради доступности. Проверки CLI здесь не утверждают, что PHP-FPM/Apache загрузили тот же php.ini. Для обычных файлов нужны права ОС; для внешней сети — политика адресов и инфраструктура.

Ресурсы, настоящие классы и прототипы callback

Страница Stream Classes исторически называет php_user_filter абстрактным. В проверенной PHP 8.5.11 это обычный предопределённый класс с overridable callbacks, и Reflection подтверждает isAbstract=false. StreamBucket — настоящий final-класс с PHP 8.4. streamWrapper — только описанный набор callbacks, поэтому extends streamWrapper использовать нельзя. Ресурсы потоков/контекстов/очередей — не экземпляры этих трёх одинаковых классов.

Отличить реальные классы от протокола документацииСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(class_exists('php_user_filter'));
var_dump((new ReflectionClass('php_user_filter'))->isAbstract());
var_dump(class_exists('StreamBucket'));
var_dump(class_exists('streamWrapper'));
var_dump(function_exists('stream_notification_callback'));
Ожидаемый результат
bool(true)
bool(false)
bool(true)
bool(false)
bool(false)

Проверка относится к PHP 8.5.11. Последнее имя — прототип вашей callback-функции в документации, а не готовая встроенная функция. Reflection только исследует структуру; методы не вызываются.

Перед своими классами пройдите class, объект и метод и наследование. Обычное чтение/копирование файлов не требует писать wrapper или subclass. Сначала используйте готовый API; свои реализации нужны при повторяющейся интеграционной задаче.

Контекст: отдельная политика каждого обращения

Чтобы два клиента имели разные таймауты/заголовки, создайте отдельные context resource. options имеют вложенную форму [wrapper][option]; parameters вроде notification принадлежат самому context. Создать массив настройки — ещё не сделать HTTP-запрос. Глобальный default context удобен только при осознанной общей политике: его изменение влияет на другой код этого выполнения.

Конфигурация становится частью открытия
  1. Массив http.timeout=2 описывает настройку
  2. stream_context_create создаёт context resource
  3. Открытие получает этот resource явным аргументом
  4. Wrapper читает свои options; другие остаются его соседями
Создать две независимые настройки без запросовСамостоятельный пример
<?php
declare(strict_types=1);

$quick = stream_context_create(['http' => ['timeout' => 2]]);
$patient = stream_context_create(['http' => ['timeout' => 8]]);
echo stream_context_get_options($quick)['http']['timeout'], "\n";
echo stream_context_get_options($patient)['http']['timeout'], "\n";
Ожидаемый результат
2
8

Ключ http выбирает wrapper, timeout его option. Два ресурса хранят две политики. Никакой URL не открыт. Возвращённый массив — снимок заданных options, а не полный список всех неявных defaults реализации.

Изменить современную вложенную настройку помогает stream_context_set_options (8.3+); stream_context_set_option сохраняет форму одного wrapper/option/value, а его перегрузка с одним массивом deprecated с 8.4. stream_context_set_params задаёт parameters; notification получает события поддерживающего wrapper и не гарантирует одинаковый список/порядок для любого протокола. Подробности defaults/merge и callback находятся в карточках.

Готовые фильтры: направление, порядок и финальный хвост

Нужно записать экспорт в нормализованном виде. Готовый filter можно вставить в цепь без собственного класса. READ обрабатывает путь от источника к вашему коду; WRITE — от кода к источнику. Для read+write режима без явного направления легко получить два преобразования, а для преобразования вроде ROT13 — визуально отменить первое.

WRITE-фильтр в экспортной операции
  1. Код передаёт tea в fwrite
  2. Цепь WRITE получает входные байты
  3. string.toupper выпускает TEA
  4. Хранилище сохраняет результат
  5. READ без фильтра возвращает TEA
Подключить готовое преобразование к записиСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $filter = stream_filter_append($stream, 'string.toupper', STREAM_FILTER_WRITE);
    fwrite($stream, 'tea');
    stream_filter_remove($filter);
    rewind($stream);
    echo stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
TEA

Это маленький ASCII-опыт. При произвольных настройках проверяют false при открытии/прикреплении/финализации; карточки показывают отказы. Удаление завершает filter, но не закрывает stream. Unicode требует подходящего алгоритма кодировки, не просто string.toupper.

Порядок цепи существенен: сначала преобразовать, потом кодировать — иной результат, чем обратный порядок. stream_filter_append добавляет в конец, stream_filter_prepend в начало; обработка уже накопленного READ-буфера у них различается. Filter может удерживать хвост, поэтому один write не всегда сразу даёт весь output. Накопление полного входа в свойстве всё равно расходует память пропорционально размеру файла.

Практика: нормализовать файл артикулов и сохранить результат

Склад прислал ASCII-артикулы tea-104 и milk-105 маленькими буквами. Наш импорт ожидает большие. Сначала готовый фильтр менял одну строку; теперь перенесём файл через ту же цепочку и покажем сохранённый результат.

ЭлементРоль в нашем файле
$fromОткрытый skus.txt: источник двух строк.
$toОткрытый normalized.txt: назначение. wb обнулит только этот учебный файл.
STREAM_FILTER_WRITEПреобразовывать данные по пути в normalized.txt.
stream_copy_to_streamПередавать данные между ресурсами, не собирая весь исходник вручную в строке.
stream_filter_removeЗавершить фильтр; затем закрыть файлы.
Создать файл нормализованных артикуловСамостоятельный пример
<?php
declare(strict_types=1);

$from = fopen(__DIR__ . '/skus.txt', 'rb');
$to = fopen(__DIR__ . '/normalized.txt', 'wb');
if ($from === false || $to === false) { throw new RuntimeException('Не открыть файлы'); }
try {
    $filter = stream_filter_append($to, 'string.toupper', STREAM_FILTER_WRITE);
    if ($filter === false) { throw new RuntimeException('Не подключить преобразование'); }
    if (stream_copy_to_stream($from, $to) === false) { throw new RuntimeException('Не завершить копирование'); }
    if (!stream_filter_remove($filter)) { throw new RuntimeException('Не завершить фильтр'); }
} finally { fclose($from); fclose($to); }
echo "До:\n", file_get_contents(__DIR__ . '/skus.txt');
echo "После:\n", file_get_contents(__DIR__ . '/normalized.txt');
Ожидаемый результат
До:
tea-104
milk-105
После:
TEA-104
MILK-105

Новый файл действительно создан в папке опыта. Демонстрационное чтение целиком показывает две короткие строки; для большого файла передавайте результат следующему потребителю потоком. Это ASCII-договор артикула; для русских названий потребуется подходящее Unicode-преобразование.

Улучшение: вместо безусловного регистра проверяйте допустимый формат каждой логической строки. Тогда блок чтения может закончиться в середине строки: потребуется сохранять небольшой незавершённый хвост. Именно эту задачу показывает следующий пользовательский фильтр. Для одного уже прочитанного короткого артикула strtoupper($sku) проще целой цепочки; Base64 при добавлении кодирования не превращает данные в секрет.

Свой фильтр: кто вызывает методы и кому принадлежат данные

Собственный php_user_filter полезен, когда библиотека умеет читать только поток, а преобразование нужно повторять для разных источников. Вы объявляете subclass, регистрируете имя, прикрепляете экземпляр. PHP задаёт filtername и params; stream доступен во время обработки, но на него нельзя полагаться при onCreate/onClose. Constructor в этом механизме пропускается.

Одна строка пересекает два входных блока
  1. Первый блок содержит te: два байта приняты
  2. Фильтр хранит pending=te и возвращает FEED_ME
  3. Второй блок содержит a + перевод строки
  4. Из pending собрана tea, новый bucket содержит TEA
  5. PASS_ON передаёт готовую строку дальше
Сохранить логическую строку при произвольных границах записиСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
stream_filter_register('lesson.lines', LessonLineFilter::class);
echo lessonFilter('lesson.lines', ['te', "a\nmi", 'lk']), "\n";
Ожидаемый результат
TEA
MILK

Полный класс и harness приложены. Каждый входной байт учитывается в consumed сразу, pending ограничен 1024 байтами, а closing выпускает последний хвост. Модель здесь ASCII line protocol, не универсальный CSV/XML/UTF-8-parser.

Подробный контракт разделён между php_user_filter::onCreate (проверка и состояние), php_user_filter::filter (данные/status) и php_user_filter::onClose (ресурсы). Не привязывайте платёж или отправку письма к числу filter-вызовов: они управляются буферами и жизненным циклом, не бизнес-командами. Для обычного изменения уже имеющейся короткой строки простая функция яснее собственной цепочки.

StreamBucket и brigade: блок, очередь и учёт байтов

Bucket — отдельный блок строки, brigade — очередь таких блоков. StreamBucket final, появился в PHP 8.4 вместо stdClass-обёртки. Поля bucket (внутренний ресурс), data (строка), dataLength (длина), историческое datalen описывают этот блок. datalen deprecated с 8.4; для учёта принятых байтов проще сохранить strlen(data) до изменения. Извлечение и передача очереди выполняются только специальными функциями.

ОперацияДвижение данных
stream_bucket_make_writeableИзвлекает первый входной bucket либо null.
stream_bucket_newСоздаёт блок новых байтов, но ещё не пишет в stream.
stream_bucket_appendСтавит блок в конец выбранной brigade.
stream_bucket_prependСтавит блок в начало текущей brigade, не меняет прошлые отправленные данные.
Проверить фактические свойства StreamBucket в PHP 8.5.11Самостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $bucket = stream_bucket_new($stream, 'tea');
    $property = new ReflectionProperty(StreamBucket::class, 'data');
    var_dump($property->isReadOnly());
    $bucket->data = 'milk';
    echo $bucket->data, "\n";
} finally { fclose($stream); }
Ожидаемый результат
bool(false)
milk

Свежий online synopsis помечает поля readonly, но tagged stub и runtime PHP 8.5.11 этого не делают. Это специально проверенный факт данной версии. Не переносите вывод на неизвестную будущую версию; изменения data перед append подтверждены отдельными работающими фильтрами.

Собственный протокол: когда wrapper оправдан

Wrapper нужен, когда существующая библиотека принимает путь или fopen, а ваш источник устроен иначе. Вы предоставляете нужные callbacks и регистрируете схему. Полный набор методов не обязателен для любого backend, но отсутствие нужного callback приводит к отказу операции. Не обещайте запись, directory listing, stat или locking только ради совместимости.

Привычный вызов становится вызовом вашего кода
  1. register связывает lesson с LessonFileBridge
  2. fopen lesson://store/tea.txt создаёт объект
  3. stream_open сохраняет внутренний файловый resource
  4. fread вызывает stream_read, с PHP-буфером между ними
  5. fclose завершает stream_flush/stream_close по состоянию

У объекта публично объявляют $context; PHP может присвоить context resource или null. stream_open получает путь/режим/options/&opened_path; read/write двигают позицию, seek/tell согласуют перемотку. stream_stat работает с открытым экземпляром, url_stat с URL. Directory callbacks хранят отдельное перечисление. Метаданные и файловые операции используют путь, а cast раскрывает underlying resource для поддерживаемых действий ОС.

До этой части нужны объявление класса и методы, области видимости и ссылки PHP. Не создавайте extends streamWrapper: это документационный prototype. В этом разделе сохранены все 25 callback-страниц, в каждой есть два исполняемых опыта через ограниченную лабораторию и ограничения её возможностей.

Полный учебный wrapper: путь, запись, позиция и чтение

Наша библиотека умеет только открывать путь. Хочется дать ей адрес lesson://store/pickup.txt, а способ хранения спрятать внутри адаптера. Начните с регистрации и чтения ниже; затем откройте приложенный wrapper-lab.php. Это отдельный файл проекта, а не встроенная часть PHP.

Знакомый вызовЧто делает приложенный класс
stream_wrapper_register("lesson", LessonFileBridge::class)Связывает слово lesson с классом. ::class получает имя класса строкой; new ещё не было.
fopen("lesson://store/pickup.txt", "rb")PHP создаёт объект и вызывает stream_open с адресом/режимом. Внутри сохраняется обычный файловый ресурс.
fread($stream, 3)Метод stream_read получает запрос байтов; PHP может читать вперёд и хранить остаток.
fseek($stream, 0)Методы stream_seek и stream_tell согласуют позицию с внутренним файлом.
fclose($stream)stream_close освобождает ресурс экземпляра; объект может жить дольше из-за внешней ссылки.

Официальный пример связывает var://имя с $GLOBALS[имя], иллюстрируя позицию и callbacks. Для учебного сервиса полезнее ограниченное хранилище: заранее заданный lesson://store/... переводится только в fixture-папку примера. Мы не даём URL выбирать произвольную глобальную переменную и не выдаём упрощённый resolver за защиту от всех файловых атак.

Записать отчёт через свою схему и снова прочитатьСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();
$stream = fopen('lesson://store/report.txt', 'w+b');
try {
    fwrite($stream, 'tea');
    rewind($stream);
    echo stream_get_contents($stream), "\n";
} finally { fclose($stream); stream_wrapper_unregister('lesson'); }
echo file_get_contents(__DIR__ . '/store/report.txt'), "\n";
Ожидаемый результат
tea
tea

Приложенный wrapper-lab.php содержит все callbacks и журнал. Первый вывод идёт через нашу схему, второй напрямую читает созданный локальный fixture: результат действительно сохранён. Временная папка удаляется проверяющим инструментом после примера.

Разберите файл по маршруту: register → __construct → stream_open → stream_write → stream_seek/stream_tell → stream_read/stream_eof → stream_close. Не все внешние вызовы обязательно порождают callback: PHP может обслужить чтение/seek из буфера. Реальная модель владения важнее числа событий. Для большого production-adapter разделяют policy адресов, хранилище, ошибки и wrapper, а не помещают всё в универсальный класс без ограничений.

Отказ, пустой результат, timeout и безопасная диагностика

Потоки унифицируют операции, но не устраняют нормальные отказы: файл отсутствует, права не подходят, схема не зарегистрирована, сеть недоступна, timeout истёк, формат фильтра испорчен. Обычно низкоуровневый отказ сопровождается E_WARNING и false; неверный тип/значение аргумента в PHP 8 может вызвать TypeError/ValueError. Пустая строка и число 0 требуют отдельной интерпретации по конкретной функции.

Различить отсутствие файла и успешную пустую строкуСамостоятельный пример
<?php
declare(strict_types=1);

$reported = false;
set_error_handler(function ($level) use (&$reported) {
    if ($level === E_WARNING) { $reported = true; return true; }
    return false;
});
try { $missing = fopen(__DIR__ . '/missing.txt', 'rb'); }
finally { restore_error_handler(); }
$empty = fopen(__DIR__ . '/empty.txt', 'rb');
try { $text = stream_get_contents($empty); }
finally { fclose($empty); }
var_dump($missing === false, $reported, $text === '');
Ожидаемый результат
bool(true)
bool(true)
bool(true)

Handler локален и обязательно восстановлен. Он не превращает открытие в успех: результат false всё равно проверен. В настоящем приложении сохраняют безопасный тип проблемы/идентификатор операции, не пароль в URL и не полный body ответа.

Временная неготовность non-blocking соединения требует select и конечного deadline; feof не заменяет ожидание. Частичная запись требует продолжения с остатка; успешный sendto не подтверждает получение UDP-сообщения. TLS true должен сочетаться с проверкой личности peer, а HTTP-ответ — с проверкой статуса/формата. Контролируйте адрес назначения и redirect, не принимайте произвольный URL посетителя как безопасный источник.

Не показывайте display_errors в публичном JSON: warning смешается с телом. Обработка ошибок и журналирование объясняет передачу диагностики; stream_get_meta_data — поля timed_out/blocked/eof. Метаданные не заменяют проверку результата операции. Для cleanup используйте finally и собственные ресурсы; не закрывайте handles вызывающего кода без договора.

Из одного интерфейса — память, файл и сжатый экспорт

Одинаковый читатель может получать данные из разных источников. Это удобно для тестирования импортёра: вместо внешнего сервиса ему дают контролируемый поток. Но read-all в stream_get_contents/file_get_contents всё равно создаёт полную строку в памяти. Для большого экспорта лучше stream_copy_to_stream или обработка ограниченных порций; parser и собственный накопитель также должны быть ограничены.

Прочитать одинаковые байты через разные wrappersСамостоятельный пример
<?php
declare(strict_types=1);

echo file_get_contents(__DIR__ . '/menu.txt'), "\n";
echo file_get_contents('data://text/plain,tea'), "\n";
Ожидаемый результат
tea
tea

Первый адрес использует file по умолчанию, второй data хранит короткое содержимое в URL. Это локальный опыт: он не выполняет HTTP/FTP и не учит хранить секреты в адресе.

Сохранить сжатый экспорт и прочитать распакованные байтыСамостоятельный пример
<?php
declare(strict_types=1);

$url = 'compress.zlib://' . __DIR__ . '/menu.gz';
$stream = fopen($url, 'wb');
if ($stream === false) { throw new RuntimeException('Compressed stream unavailable'); }
try { fwrite($stream, "tea\nmilk\n"); }
finally { fclose($stream); }
echo file_get_contents($url);
Ожидаемый результат
tea
milk

Требуется zlib. Закрытие завершает сжатый формат. Это реальное создание временного .gz в папке примера, затем чтение через тот же wrapper; размер/скорость сжатия крошечного текста не используется как benchmark.

В официальной подборке также есть HTTP/HTTPS/FTP/FTPS и ручная сборка POST поверх socket. В реальном сервисе не вставляйте user:password в URL и не считайте сам fsockopen TLS-проверкой всей прикладной команды. HTTP требует корректного Host, длины в байтах, CRLF, разбора статуса/headers/body, частичных I/O и ограничений. Используйте готовый HTTP wrapper/клиент и разбор внешних ответов; низкоуровневые TCP/TLS-механизмы здесь проверены только на изолированных localhost-лабораториях.

Полный набор констант: флаги, режимы, события и статусы

Ниже сохранены все 91 точное имя из официальной страницы constants. Одни значения — отдельные альтернативы, другие — битовые флаги; не объединяйте | всё подряд. Существование имени не гарантирует поддержку операции ОС, транспортом или OpenSSL. Для версии/особенностей конкретной операции переходите в связанную карточку.

ИменаНазначение / связанная операция
STREAM_CLIENT_CONNECTКлиент устанавливает соединение; включают и вместе с ASYNC_CONNECT. stream_socket_client
STREAM_CLIENT_ASYNC_CONNECTАсинхронное начало соединения, совместно с CONNECT; готовность к записи ещё требует обработки исхода соединения. stream_socket_client
STREAM_CLIENT_PERSISTENTЗапрос повторного использования persistent-ресурса между запросами процесса; не общая сессия всех workers. stream_socket_client
STREAM_SERVER_BINDСвязать серверный socket с адресом. stream_socket_server
STREAM_SERVER_LISTENСлушать соединения TCP; не применяют к UDP. stream_socket_server
STREAM_SHUT_RD
STREAM_SHUT_WR
STREAM_SHUT_RDWR
Запретить дальнейшее чтение / запись / оба направления. Это альтернативы how, не свободно комбинируемые флаги. stream_socket_shutdown
STREAM_OOBОбработка out-of-band данных, если транспорт поддерживает. Не обычная срочность любого прикладного сообщения. stream_socket_recvfrom
STREAM_PEEKПрочитать доступные socket-данные, не удалив их из receive buffer. Не допустим для sendto. stream_socket_recvfrom
STREAM_FILTER_READ
STREAM_FILTER_WRITE
STREAM_FILTER_ALL
Цепь чтения / записи / обе; ALL = READ | WRITE, при обеих создаются разные экземпляры. stream_filter_append
STREAM_MUST_SEEK
STREAM_IGNORE_URL
Внутренние, в этом публичном API не используемые флаги: требование seekability (возможна копия), запрет plugin wrappers. Не передавайте произвольно в fopen. stream_get_meta_data
STREAM_PF_INET
STREAM_PF_INET6
STREAM_PF_UNIX
Семейство IPv4 / IPv6 / локальные Unix-протоколы. Наличие имени не гарантирует работу family на этой ОС. stream_socket_pair
STREAM_SOCK_DGRAMДатаграммы с границами сообщений, например UDP. stream_socket_pair
STREAM_SOCK_STREAMУпорядоченный двунаправленный поток байтов, например TCP; границ прикладных сообщений нет. stream_socket_pair
STREAM_SOCK_RAWНизкоуровневый raw socket; обычно требуются дополнительные права ОС. stream_socket_pair
STREAM_SOCK_RDMТип reliably-delivered message socket; доступность зависит от ОС/транспорта. stream_socket_pair
STREAM_SOCK_SEQPACKETУпорядоченные пакеты с границами; доступность зависит от ОС/транспорта. stream_socket_pair
STREAM_IPPROTO_ICMP
STREAM_IPPROTO_IP
STREAM_IPPROTO_RAW
STREAM_IPPROTO_TCP
STREAM_IPPROTO_UDP
Выбор протокола ICMP / IP / raw / TCP / UDP для совместимого family/type. Не маска сразу всех протоколов. stream_socket_pair
STREAM_NOTIFY_RESOLVEИмя сообщает разрешение адреса, но официально генерация этого уведомления ещё не реализована. stream_notification_callback
STREAM_NOTIFY_CONNECTСоединение с ресурсом установлено; это не подтверждение успеха HTTP-команды. stream_notification_callback
STREAM_NOTIFY_AUTH_REQUIREDРесурс требует дополнительной аутентификации; часто severity ERR. stream_notification_callback
STREAM_NOTIFY_AUTH_RESULTАутентификация завершена с успехом или отказом; надо смотреть детали, не само наличие события. stream_notification_callback
STREAM_NOTIFY_MIME_TYPE_ISНайден MIME-тип; описание в message. Это заявленные метаданные, не проверка безопасного содержимого. stream_notification_callback
STREAM_NOTIFY_FILE_SIZE_ISОбнаружен размер ресурса; он может быть неизвестен заранее. stream_notification_callback
STREAM_NOTIFY_REDIRECTEDПолучено перенаправление; адрес в message. Проверяйте допустимость переходов по сетевой политике. stream_notification_callback
STREAM_NOTIFY_PROGRESSПрогресс в bytes_transferred и, если известен, bytes_max. Не обещает одинаковое число событий. stream_notification_callback
STREAM_NOTIFY_COMPLETEDДанных больше нет; событие впервые реализовано в PHP 8.3. Не заменяет проверку статуса протокола. stream_notification_callback
STREAM_NOTIFY_FAILUREОбщий отказ; message и message_code помогают диагностике без публикации секретов. stream_notification_callback
STREAM_NOTIFY_SEVERITY_INFO
STREAM_NOTIFY_SEVERITY_WARN
STREAM_NOTIFY_SEVERITY_ERR
Информационное событие / некритичная проблема / критичный отказ. Это уровни severity, не error_reporting E_* и не flags. stream_notification_callback
STREAM_IS_URLПометить регистрируемую схему URL-протоколом с правилами allow_url_*; 0 обозначает local. Не валидирует адрес. stream_wrapper_register
STREAM_CAST_FOR_SELECT
STREAM_CAST_AS_STREAM
Запрос underlying ресурса для stream_select / для иного использования. Callback streamWrapper::stream_select не существует. streamWrapper::stream_cast
STREAM_META_TOUCHЗапрос touch; value — времена [mtime,atime] либо пустой массив для текущего времени. streamWrapper::stream_metadata
STREAM_META_OWNER
STREAM_META_OWNER_NAME
Запрос владельца по целому ID / строковому имени. streamWrapper::stream_metadata
STREAM_META_GROUP
STREAM_META_GROUP_NAME
Запрос группы по целому ID / строковому имени. streamWrapper::stream_metadata
STREAM_META_ACCESSЗапрос chmod; value — целый mode с правилами платформы. streamWrapper::stream_metadata
STREAM_MKDIR_RECURSIVEФлаг рекурсивного создания. Упоминается в prototype options mkdir/rmdir; обычный rmdir не получает от этого рекурсивный режим. streamWrapper::mkdir
STREAM_USE_PATHПоиск относительно include_path; wrapper обязан вернуть открытый полный путь по ссылке при успешном поиске. streamWrapper::stream_open
STREAM_REPORT_ERRORSWrapper должен сообщать ошибки открытия; если флаг не установлен, не создавать сообщения. streamWrapper::stream_open
STREAM_OPTION_BLOCKINGЗапрос blocking: arg1=1/0, arg2 в PHP 8.5.11 null. streamWrapper::stream_set_option
STREAM_OPTION_READ_TIMEOUTЗапрос таймаута: секунды и микросекунды. Фраза «read buffering» на странице constants — неточность, это ожидание. streamWrapper::stream_set_option
STREAM_OPTION_READ_BUFFER
STREAM_OPTION_WRITE_BUFFER
Запрос read/write buffering: mode и размер в callback. streamWrapper::stream_set_option
STREAM_BUFFER_NONE
STREAM_BUFFER_LINE
STREAM_BUFFER_FULL
Нет / построчная / полная буферизация. Наличие LINE не обещает поддержку любым пользовательским setter/источником. streamWrapper::stream_set_option
STREAM_URL_STAT_LINKВернуть сведения о самой ссылке, а не о её цели. streamWrapper::url_stat
STREAM_URL_STAT_QUIETНе сообщать ошибки в тихой stat-проверке. streamWrapper::url_stat
PSFS_PASS_ON
PSFS_FEED_ME
PSFS_ERR_FATAL
Статус filter: выход готов / нужны ещё данные / невосстановимый отказ. Это взаимоисключающие результаты, не произвольная OR-маска. php_user_filter::filter
PSFS_FLAG_NORMAL
PSFS_FLAG_FLUSH_INC
PSFS_FLAG_FLUSH_CLOSE
Внутренние флаги обычной обработки / промежуточного flush / финального flush. Пользовательский filter получает bool closing, не эту тройку как аргумент. php_user_filter::filter
STREAM_CRYPTO_METHOD_ANY_CLIENTЛюбой SSL/TLS, допускаемый реализацией; слишком широкий выбор не является рекомендацией безопасности. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_SSLv2_CLIENTИсторический SSL 2, небезопасен и обычно отключён. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_SSLv3_CLIENTИсторический SSL 3, небезопасен и обычно отключён. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_SSLv23_CLIENTИсторическое имя согласования нескольких версий; не современная политика разрешённых TLS. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLS_CLIENTНабор TLS-версий реализации; для ограничения задают конкретные современные версии. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLSv1_0_CLIENTИсторический TLS 1.0; не выбирают для нового сервиса. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLSv1_1_CLIENTИсторический TLS 1.1; не выбирают для нового сервиса. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLSv1_2_CLIENTTLS 1.2, при поддержке криптографической библиотекой. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLSv1_3_CLIENTTLS 1.3; эта константа доступна с PHP 7.4, зависит от библиотеки. Роль клиента. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_ANY_SERVERЛюбой SSL/TLS, допускаемый реализацией; слишком широкий выбор не является рекомендацией безопасности. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_SSLv2_SERVERИсторический SSL 2, небезопасен и обычно отключён. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_SSLv3_SERVERИсторический SSL 3, небезопасен и обычно отключён. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_SSLv23_SERVERИсторическое имя согласования нескольких версий; не современная политика разрешённых TLS. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLS_SERVERНабор TLS-версий реализации; для ограничения задают конкретные современные версии. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLSv1_0_SERVERИсторический TLS 1.0; не выбирают для нового сервиса. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLSv1_1_SERVERИсторический TLS 1.1; не выбирают для нового сервиса. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLSv1_2_SERVERTLS 1.2, при поддержке криптографической библиотекой. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_METHOD_TLSv1_3_SERVERTLS 1.3; эта константа доступна с PHP 7.4, зависит от библиотеки. Роль сервера. stream_socket_enable_crypto
STREAM_CRYPTO_PROTO_SSLv3Псевдоним STREAM_CRYPTO_METHOD_SSLv3_SERVER; не отдельный режим проверки сертификата. stream_socket_enable_crypto
STREAM_CRYPTO_PROTO_TLSv1_0Псевдоним STREAM_CRYPTO_METHOD_TLSv1_0_SERVER; не отдельный режим проверки сертификата. stream_socket_enable_crypto
STREAM_CRYPTO_PROTO_TLSv1_1Псевдоним STREAM_CRYPTO_METHOD_TLSv1_1_SERVER; не отдельный режим проверки сертификата. stream_socket_enable_crypto
STREAM_CRYPTO_PROTO_TLSv1_2Псевдоним STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; не отдельный режим проверки сертификата. stream_socket_enable_crypto
STREAM_CRYPTO_PROTO_TLSv1_3Псевдоним STREAM_CRYPTO_METHOD_TLSv1_3_SERVER; не отдельный режим проверки сертификата. stream_socket_enable_crypto
Отличить маску направлений от отдельного значенияСамостоятельный пример
<?php
declare(strict_types=1);

$directions = STREAM_FILTER_READ | STREAM_FILTER_WRITE;
var_dump($directions === STREAM_FILTER_ALL);
var_dump(($directions & STREAM_FILTER_READ) !== 0);
var_dump(($directions & STREAM_FILTER_WRITE) !== 0);
Ожидаемый результат
bool(true)
bool(true)
bool(true)

| объединяет независимые биты, & проверяет наличие. Это подходит указанным FILTER_* флагам; семейство PF_* и статусы PSFS_* нельзя так же смешивать без контракта.

Практика: ограниченный импорт и проверка граничных случаев

Упражнение: источник может быть больше допустимого меню. Прочитайте максимум лимит+1 байт, чтобы отличить подходящий результат от превышения; не возвращайте молча обрезанный документ как успешный. Затем объясните, почему это ограничивает прочитанный фрагмент, но не весь расход памяти приложения или сетевой backend.

Проверка размера до принятия документа
  1. Лимит меню равен 3 байтам
  2. Читатель запрашивает не больше 4 байтов
  3. tea имеет 3 — принимаем
  4. tea+ имеет 4 — сообщаем превышение, не отдаём обрезок
Возможное решение проверки небольшого документаСамостоятельный пример
<?php
declare(strict_types=1);

function readSmallMenu($stream, int $limit): string {
    if ($limit < 0 || $limit > 1024) { throw new InvalidArgumentException('Invalid limit'); }
    $text = stream_get_contents($stream, $limit + 1);
    if ($text === false) { throw new RuntimeException('Read failed'); }
    if (strlen($text) > $limit) { throw new LengthException('Menu is too large'); }
    return $text;
}
foreach (['tea', 'tea+milk'] as $input) {
    $stream = fopen('php://memory', 'w+');
    try {
        fwrite($stream, $input); rewind($stream);
        try { echo readSmallMenu($stream, 3), "\n"; }
        catch (LengthException $error) { echo "Меню превышает лимит\n"; }
    } finally { fclose($stream); }
}
Ожидаемый результат
tea
Меню превышает лимит

Функция не закрывает чужой ресурс: владелец foreach делает это в finally. Верхняя граница limit предотвращает переполнение limit+1. Опыт рассчитан на готовый локальный источник; для живого non-blocking потока отдельно нужны ожидание, длина сообщения и общий deadline.

Следующий шаг: прогоните ASCII-фильтр строк с разными разбиениями одного input и сравните результат, затем вызовите отказ на строке длиннее 1024. Для wrapper добавьте только действительно нужную операцию и тестируйте её контракт, а не return true по умолчанию. Ни одно из этих упражнений не измеряет production throughput; производительность зависит от количества копий, размера/числа I/O, памяти алгоритма и поведения источника.

Все функции и callbacks семейства Streams

Сначала выбирайте действие: прочитать/скопировать, задать context, дождаться сети, преобразовать filter, реализовать wrapper. Ниже полный API этой официальной категории, включая исторический alias и документационные callbacks; stream_context_create сохраняет прежний адрес в начальной главе.

ОперацияЗадача
stream_context_createСоздать отдельный набор настроек для одного маршрута чтения или записи.
stream_context_get_optionsПосмотреть сохранённые wrapper-настройки ресурса, не открывая соединение.
stream_context_set_optionУстановить одно значение wrapper-настройки в существующем контексте или потоке.
stream_context_set_optionsДополнить контекст набором wrapper-настроек без устаревшей перегрузки set_option.
stream_context_get_paramsПолучить параметры контекста вместе с wrapper-options, например для диагностики регистрации notification.
stream_context_set_paramsЗарегистрировать параметры контекста, прежде всего callback уведомлений.
stream_context_get_defaultПолучить общий контекст, понимая изменение состояния при переданных options.
stream_context_set_defaultЯвно дополнить общую политику контекста там, где код владеет всем процессом.
stream_notification_callbackРеализовать свой обработчик уведомлений о работе HTTP/FTP-потока.
stream_get_contentsПрочитать оставшиеся байты уже открытого потока в строку, учитывая позицию и память.
stream_copy_to_streamПеренести байты между открытыми ресурсами и проверить фактически скопированный объём.
stream_get_lineПрочитать фрагмент до выбранного разделителя без включения разделителя в результат.
stream_get_meta_dataПолучить диагностическое состояние открытого потока и учитывать различия wrapper.
stream_resolve_include_pathРазрешить имя своего файла по include_path, не исполняя его.
stream_supports_lockУзнать поддержку flock перед выбором стратегии синхронизации.
stream_is_localРазличить классификацию local/URL wrapper без ложного обещания безопасности пути.
stream_isattyПроверить терминальное назначение CLI-потока, чтобы не загрязнять перенаправленный вывод.
stream_get_transportsПолучить перечень зарегистрированных транспортов сокетов для выбора поддерживаемого маршрута.
stream_get_wrappersУзнать зарегистрированные схемы открытия потоков, отделяя доступность от разрешённости.
stream_set_chunk_sizeНастроить внутреннюю порцию потока и сохранить прежнее значение для восстановления.
stream_set_read_bufferПопросить wrapper изменить буферизацию чтения и проверить, принял ли он запрос.
stream_set_write_bufferЗапросить размер буферизации записи, предусмотрев неподдерживаемый wrapper.
stream_set_blockingВыбрать, должен ли поддерживаемый поток ждать данные, и организовать продолжение отдельно.
stream_set_timeoutОграничить ожидание данных в поддерживаемом потоке и после операции проверить timed_out.
stream_selectДождаться готовности потоков и обработать изменённые наборы без предположения о полной передаче.
stream_socket_serverСоздать локальный адрес для входящих соединений, отделяя listener от принятого клиента.
stream_socket_clientОткрыть клиентский stream к выбранному адресу и отдельно вести протокол обмена.
stream_socket_acceptПринять одну входящую TCP-подобную связь и получить отдельный ресурс клиента.
stream_socket_get_nameПолучить локальное или удалённое имя существующего socket stream.
stream_socket_pairСоздать два соединённых socket stream для локального двустороннего обмена.
stream_socket_shutdownЗавершить выбранное направление full-duplex связи, отделяя это от закрытия ресурса.
stream_socket_recvfromПолучить байты непосредственно из сокета, при необходимости вместе с адресом отправителя.
stream_socket_sendtoПередать сообщение выбранному socket-адресату и проверить число отправленных байтов.
stream_socket_enable_cryptoСогласовать TLS на уже открытом socket stream, различая успех, отказ и продолжение non-blocking handshake.
stream_filter_appendДобавить преобразование в конец цепочки чтения или записи открытого потока.
stream_filter_prependДобавить преобразование в начало цепочки чтения или записи открытого потока.
stream_filter_registerСвязать имя фильтра с классом, который PHP будет создавать для цепочки потока.
stream_filter_removeЗавершить накопленные данные и отсоединить конкретный фильтр, сохранив поток открытым.
stream_get_filtersПолучить зарегистрированные имена и семейства фильтров текущей сборки и выполнения PHP.
stream_bucket_appendПоместить обработанный блок в конец выходной очереди фильтра.
stream_bucket_prependВставить блок в начало текущей очереди, не меняя уже отправленную часть потока.
stream_bucket_make_writeableИзвлечь следующий входной блок для изменения либо получить null, когда очередь пуста.
stream_bucket_newСоздать отдельный блок байтов для последующей передачи из пользовательского фильтра.
php_user_filter::filterПринять входные блоки, учесть байты и выдать преобразованные блоки либо запросить следующий вход.
php_user_filter::onCreateПроверить параметры и подготовить состояние экземпляра до обработки данных.
php_user_filter::onCloseОсвободить ресурсы экземпляра после завершения фильтра, не формируя новые выходные данные.
stream_wrapper_registerСвязать собственную схему URL с классом-обработчиком потоков.
stream_register_wrapperСвязать собственную схему URL с классом-обработчиком потоков.
stream_wrapper_unregisterУдалить схему из реестра будущих открытий, сохранив жизнь уже открытых потоков.
stream_wrapper_restoreВернуть встроенный обработчик схемы PHP после его отмены или подмены.
streamWrapper::stream_openПодготовить экземпляр wrapper для конкретного адреса и режима открытия.
streamWrapper::stream_readВернуть следующую порцию байтов из источника и обновить его позицию.
streamWrapper::stream_writeЗаписать доступное количество входных байтов и вернуть точное число принятых.
streamWrapper::stream_seekИзменить позицию доступного для перемотки источника относительно начала, текущей позиции или конца.
streamWrapper::stream_tellВернуть текущую внутреннюю позицию источника для согласования операций seek.
streamWrapper::stream_eofСообщить настоящий конец источника без потребления данных.
streamWrapper::stream_truncateИзменить размер открытого хранилища по запросу ftruncate.
streamWrapper::__constructПодготовить независимый объект обработчика перед операцией над адресом.
streamWrapper::__destructЗавершить жизнь объекта wrapper, различая её с закрытием самого потока.
streamWrapper::stream_closeОсвободить ресурсы конкретного открытого потока при fclose.
streamWrapper::stream_flushСбросить собственный кэш wrapper в нижележащее хранилище.
streamWrapper::stream_statСообщить сведения stat для уже открытого stream resource.
streamWrapper::url_statПолучить stat-сведения по адресу, включая тихие проверки существования и сведения о ссылке.
streamWrapper::stream_castПредоставить PHP внутренний ресурс, пригодный для запрошенного низкоуровневого действия.
streamWrapper::stream_lockВыполнить настоящую совместимую блокировку источника либо честно отказать.
streamWrapper::stream_set_optionПеревести запрос blocking, таймаута или буфера в настройку underlying ресурса.
streamWrapper::stream_metadataИзменить время, режим или владельца по URL согласно конкретному option.
streamWrapper::dir_opendirПодготовить отдельный источник имён каталога по URL.
streamWrapper::dir_readdirВернуть следующее имя каталога либо строго false при завершении обхода.
streamWrapper::dir_rewinddirСбросить iterator каталога к началу для следующего readdir.
streamWrapper::dir_closedirОсвободить ресурсы открытого перечисления каталога.
streamWrapper::mkdirСоздать каталог в хранилище wrapper с учётом режима и запроса рекурсивного создания.
streamWrapper::renameПереименовать файл или каталог, проверив исходный и целевой адреса.
streamWrapper::rmdirУдалить поддерживаемый каталог, не превращая вызов в неявное рекурсивное удаление.
streamWrapper::unlinkУдалить файл по адресу в пределах возможностей и полномочий wrapper.

Что ломалось в реальных задачах и как это воспроизвести

Следующие обсуждения использованы как постановки проблем. Примеры написаны для этого учебника; старые ответы не исполнялись и не перенесены как готовые решения.

ПроблемаПричина и современное решение
Записано успешно, но остаток потока пустПозиция после записи в конце. Для повторного чтения перематываем тот же совместимый ресурс; пустой остаток не означает утрату записи. Договор PHP.
Зачем filter вызывается с пустой входной очередьюЧисло вызовов задаётся механизмом потока, включая финализацию. Состояние и closing важнее предположения «один вызов на одну запись». Договор PHP.
Как узнать размер временного потокаРазмер данных и текущая позиция различаются. Для совместимого открытого ресурса fstat даёт size, а ftell позицию читателя. Договор PHP.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

stream_context_get_optionsПосмотреть сохранённые wrapper-настройки ресурса, не открывая соединение.

PHP 4.3.0+ · standard

stream_context_get_options(resource $stream_or_context): array

Посмотреть сохранённые wrapper-настройки ресурса, не открывая соединение.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
stream_or_contextОткрытый поток или ресурс контекста. Закрытый ресурс/иной тип не годится.
ВозвратАссоциативный массив wrapper → option → value; пустой, если настроек нет.
ГраницаНе подтверждает применение поля wrapper и не возвращает параметры notification. Для них get_params. Неверный ресурс даёт TypeError.
Читаем настройку, не выполняем запрос
  1. В context сохранён timeout=2
  2. get_options возвращает массив
  3. Приложение читает только безопасное поле
  4. Запрос выполняет отдельное открытие

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

Прочитать известное поле без печати всех настроекСамостоятельный пример
<?php
declare(strict_types=1);

$context = stream_context_create(['http' => ['timeout' => 4]]);
$options = stream_context_get_options($context);
echo 'Timeout: ', $options['http']['timeout'], "\n";
Ожидаемый результат
Timeout: 4

Контекст может содержать Authorization, cookies, proxy password и тело запроса. Для диагностики выбирают безопасные поля, а не var_dump всего ресурса в публичную страницу.

Изменение снимка не перенастраивает ресурсСамостоятельный пример
<?php
declare(strict_types=1);

$context = stream_context_create(['http' => ['timeout' => 2]]);
$copy = stream_context_get_options($context);
$copy['http']['timeout'] = 99;
echo stream_context_get_options($context)['http']['timeout'], "\n";
stream_context_set_option($context, 'http', 'timeout', 3);
echo stream_context_get_options($context)['http']['timeout'], "\n";
Ожидаемый результат
2
3

Присваивание числового элемента снимка не записывает его обратно. Для перенастройки предназначен setter. Это не обещание глубокой копии произвольных вложенных объектов/ресурсов в options.

Связанные операции: stream_context_get_params, stream_context_set_option, stream_context_set_options.

Маршрут от первого потока к контекстам и фильтрам.

stream_context_set_optionУстановить одно значение wrapper-настройки в существующем контексте или потоке.

PHP 4.3.0+ · standard

stream_context_set_option(resource $stream_or_context, string $wrapper, string $option_name, mixed $value): bool

Установить одно значение wrapper-настройки в существующем контексте или потоке.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
stream_or_contextПоток или контекст, который изменяется.
wrapper / option_nameСтроковые имена группы и поля, например http / timeout.
valueЗначение нужного типа по контракту wrapper. Сохранение не проверяет все будущие условия транспорта.
Возвратtrue при успешном сохранении, документированный bool. Неверный ресурс/типы дают TypeError.
Альтернативная форма($context, array $options) deprecated с 8.4; используйте set_options в 8.3+.
Изменяем один ключ
  1. Context содержит GET и timeout=2
  2. Setter заменяет только timeout на 5
  3. GET остаётся
  4. Следующая операция получает обновлённый пакет

Нужно изменить только timeout одного ресурса, не трогая его method и остальные поля. Четырёхаргументный set_option записывает одну настройку. Имя wrapper не всегда равно схеме адреса: HTTPS-запрос всё равно использует группу http для метода и timeout.

Изменить одну настройкуСамостоятельный пример
<?php
declare(strict_types=1);

$context = stream_context_create(['http' => ['method' => 'GET', 'timeout' => 2]]);
$changed = stream_context_set_option($context, 'http', 'timeout', 5);
$options = stream_context_get_options($context)['http'];
var_dump($changed);
echo $options['method'], '/', $options['timeout'], "\n";
Ожидаемый результат
bool(true)
GET/5

Название поля и его значение передаются отдельными аргументами. Метод GET сохранился; новая настройка ещё не означает новый запрос или перенастройку уже установленного TLS-сеанса.

Увидеть общий ресурс за двумя переменнымиСамостоятельный пример
<?php
declare(strict_types=1);

$first = stream_context_create(['http' => ['timeout' => 2]]);
$second = $first;
stream_context_set_option($second, 'http', 'timeout', 7);
echo stream_context_get_options($first)['http']['timeout'], "\n";
Ожидаемый результат
7

Присваивание ресурса копирует возможность обратиться к нему, не создаёт отдельный контекст. Для независимой операции создайте новый context с нужными настройками. Не меняйте переданный библиотеке ресурс без договора с вызывающим кодом.

Связанные операции: stream_context_set_options, stream_context_create, stream_context_get_options.

Маршрут от первого потока к контекстам и фильтрам.

stream_context_set_optionsДополнить контекст набором wrapper-настроек без устаревшей перегрузки set_option.

PHP 8.3.0+ · standard

stream_context_set_options(resource $context, array $options): true

Дополнить контекст набором wrapper-настроек без устаревшей перегрузки set_option.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
contextКонтекст или открытый поток.
optionsМассив wrapper → option → value. Переданные поля заменяются/добавляются; отсутствие поля не удаляет старое.
Возврат / ошибкиСовременная сигнатура возвращает true. Краткий текст docs с false — общее старое описание; неверная форма даёт ValueError, тип/ресурс — TypeError.
ВерсияPHP 8.3+. Старый двухаргументный set_option deprecated с PHP 8.4.
Массовое дополнение
  1. В context уже GET
  2. Добавили timeout и follow_location
  3. В resource теперь три поля
  4. Сохранение отличается от запроса

Настроек несколько: method, timeout и ограничение редиректов. Начиная с PHP 8.3 отдельная функция принимает массив групп. Она дополняет/заменяет переданные поля, а не очищает остальные группы и не проверяет безопасность всей политики.

Добавить несколько полей, сохранив прежнееСамостоятельный пример
<?php
declare(strict_types=1);

$context = stream_context_create(['http' => ['method' => 'GET']]);
$ok = stream_context_set_options($context, ['http' => ['timeout' => 2, 'follow_location' => 0]]);
$options = stream_context_get_options($context)['http'];
var_dump($ok);
echo $options['method'], '/', $options['timeout'], '/', $options['follow_location'], "\n";
Ожидаемый результат
bool(true)
GET/2/0

PHP 8.3+. Не переданное method сохранилось. follow_location=0 — будущая настройка HTTP-wrapper; этот пример пока проверяет только её сохранение, а не выполнение HTTP.

Отличить вложенную структуру от плоского спискаСамостоятельный пример
<?php
declare(strict_types=1);

$context = stream_context_create();
try { stream_context_set_options($context, ['timeout' => 2]); }
catch (ValueError $error) { echo "Нужна группа wrapper\n"; }
stream_context_set_options($context, ['http' => ['timeout' => 2]]);
echo stream_context_get_options($context)['http']['timeout'], "\n";
Ожидаемый результат
Нужна группа wrapper
2

Проверяется форма [wrapper => [option => value]]. Из ошибки формы не следует, что PHP отвергнет любое неизвестное имя option. Не используйте setter как валидатор чужой конфигурации; список разрешённых настроек выбирает приложение.

Связанные операции: stream_context_set_option, stream_context_get_options.

Маршрут от первого потока к контекстам и фильтрам.

stream_context_get_paramsПолучить параметры контекста вместе с wrapper-options, например для диагностики регистрации notification.

PHP 5.3.0+ · standard

stream_context_get_params(resource $context): array

Получить параметры контекста вместе с wrapper-options, например для диагностики регистрации notification.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
contextРесурс контекста или открытого потока.
ВозвратМассив параметров, содержащий options и зарегистрированные поддерживаемые параметры, например notification.
Ошибки / границаНеверный или закрытый ресурс даёт TypeError. Возвращаемые данные могут содержать секреты; чтение callback не исполняет.
Два слоя данных
  1. notification — обработчик событий
  2. options.http.timeout — настройка HTTP
  3. get_params возвращает оба слоя
  4. Приложение читает только нужные поля

Кроме настроек wrapper, контекст может нести callback уведомлений. get_params позволяет посмотреть оба слоя: отдельное поле notification и вложенный массив options. Параметры не нужно искать внутри http.

Посмотреть options в общем описании контекстаСамостоятельный пример
<?php
declare(strict_types=1);

$context = stream_context_create(['http' => ['timeout' => 2]]);
$params = stream_context_get_params($context);
echo $params['options']['http']['timeout'], "\n";
var_dump(array_key_exists('notification', $params));
Ожидаемый результат
2
bool(false)

Без зарегистрированного notification его ключ отсутствует. Нельзя требовать от любого context одинаковый набор необязательных параметров.

Проверить регистрацию своего callback без его запускаСамостоятельный пример
<?php
declare(strict_types=1);

$notify = function ($code, $severity, $message, $messageCode, $transferred, $maximum) {};
$context = stream_context_create(['http' => ['timeout' => 2]], ['notification' => $notify]);
$params = stream_context_get_params($context);
var_dump($params['notification'] === $notify);
echo $params['options']['http']['timeout'], "\n";
Ожидаемый результат
bool(true)
2

Замыкание здесь хранится как обработчик будущего события. Чтение параметров его не вызывает и не загружает файл. В реальном коде callback не должен печатать секретные сообщения внешнего сервера без фильтрации.

Связанные операции: stream_context_get_options, stream_context_set_params, stream_notification_callback.

Маршрут от первого потока к контекстам и фильтрам.

stream_context_set_paramsЗарегистрировать параметры контекста, прежде всего callback уведомлений.

PHP 4.3.0+ · standard

stream_context_set_params(resource $context, array $params): true

Зарегистрировать параметры контекста, прежде всего callback уведомлений.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
contextРесурс потока или контекста.
params.notificationВыбранный приложением callable уведомлений; HTTP/FTP, без обещания одинакового набора событий у всех операций.
params.optionsWrapper → option → value; это вложенный массив настроек.
Возврат / ошибкиСовременная сигнатура true; неправильные типы/структура приводят к исключениям, не к надёжному false-протоколу.
Меняем подписку одного context
  1. Приложение создало callable
  2. set_params сохраняет его как notification
  3. HTTP/FTP wrapper позже сообщает события
  4. Callback обрабатывает только нужные коды

Обработчик прогресса можно подключить после создания контекста. set_params изменяет общие параметры. Поддерживаемые поля этой страницы — notification и options; произвольное поле business_id не превращается в новый API уведомления.

Добавить обработчик и timeout одним набором параметровСамостоятельный пример
<?php
declare(strict_types=1);

$context = stream_context_create();
$notify = function ($code, $severity, $message, $messageCode, $transferred, $maximum) {};
$ok = stream_context_set_params($context, [
    'notification' => $notify,
    'options' => ['http' => ['timeout' => 3]],
]);
$params = stream_context_get_params($context);
var_dump($ok, $params['notification'] === $notify);
echo $params['options']['http']['timeout'], "\n";
Ожидаемый результат
bool(true)
bool(true)
3

notification находится рядом с options, не внутри него. Пустое тело callback намеренно ничего не делает: опыт показывает регистрацию. Реальные события возникают при поддерживающей их операции.

Поменять обработчик конкретной операцииСамостоятельный пример
<?php
declare(strict_types=1);

$first = function ($code, $severity, $message, $messageCode, $transferred, $maximum) {};
$second = function ($code, $severity, $message, $messageCode, $transferred, $maximum) {};
$context = stream_context_create(null, ['notification' => $first]);
stream_context_set_params($context, ['notification' => $second]);
var_dump(stream_context_get_params($context)['notification'] === $second);
Ожидаемый результат
bool(true)

Два callback не складываются в очередь: новое значение заменило прежнее. Если нужны несколько наблюдателей, ваш один обработчик должен явно вызвать их по выбранной политике.

Уведомления поддерживаются HTTP/FTP wrappers; это не универсальные события для любого fopen локального файла. options внутри params имеют тот же вложенный формат, что у create. Неправильные типы/ресурсы дают TypeError, неправильная форма options — ValueError. Успех регистрации не означает, что callback уже был вызван или что будущий запрос завершится. Не используйте внешнюю строку как произвольное имя вызываемой функции.

Связанные операции: stream_context_get_params, stream_context_create, stream_notification_callback.

Маршрут от первого потока к контекстам и фильтрам.

stream_context_get_defaultПолучить общий контекст, понимая изменение состояния при переданных options.

PHP 5.1.0+ · standard

stream_context_get_default(?array $options = null): resource

Получить общий контекст, понимая изменение состояния при переданных options.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
optionsnull/пропуск — получить существующий default; массив wrapper → option → value дополнительно записывает поля.
ВозвратРесурс общего stream-context, который используют подходящие открытия без явного context.
Версии / ошибкиPHP 5.1+; nullable options с 8.0. Неправильная вложенность ValueError, тип TypeError.
ГраницаНе создаёт независимый clone и не гарантирует, что уже открытый поток заново применит настройки.
Общая настройка процесса
  1. get_default получает один ресурс
  2. Переданные options меняют его
  3. Следующее открытие без явного context видит общую политику
  4. Отдельный create позволяет избежать этой связи

У процесса есть контекст по умолчанию: его используют открытия без явно переданного context. get_default возвращает именно этот общий ресурс. Если передать options, функция одновременно изменит его — название get не означает безусловно чистое чтение.

Убедиться, что default — один общий ресурсСамостоятельный пример
<?php
declare(strict_types=1);

$first = stream_context_get_default();
$second = stream_context_get_default();
var_dump($first === $second);
Ожидаемый результат
bool(true)

Две переменные обращаются к одному общему контексту. Контекст, созданный через create, был бы отдельным ресурсом.

Отличить настройку default от отдельного contextСамостоятельный пример
<?php
declare(strict_types=1);

$default = stream_context_get_default(['http' => ['timeout' => 4]]);
$separate = stream_context_create(['http' => ['timeout' => 1]]);
echo stream_context_get_options(stream_context_get_default())['http']['timeout'], "\n";
echo stream_context_get_options($separate)['http']['timeout'], "\n";
Ожидаемый результат
4
1

Это отдельный учебный CLI-процесс: глобальная настройка заканчивается вместе с ним. В библиотеке такой setter-through-get может неожиданно изменить последующие открытия другого кода. Не делайте это скрыто; передавайте отдельный context.

Связанные операции: stream_context_create, stream_context_set_default, stream_context_get_options.

Маршрут от первого потока к контекстам и фильтрам.

stream_context_set_defaultЯвно дополнить общую политику контекста там, где код владеет всем процессом.

PHP 5.3.0+ · standard

stream_context_set_default(array $options): resource

Явно дополнить общую политику контекста там, где код владеет всем процессом.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
optionsВложенный массив wrapper → option → value. Не переданные поля остаются.
ВозвратОбщий ресурс default context, не прежнее состояние и не bool.
ОшибкиНеверная вложенность ValueError; неправильный тип TypeError.
ЭффектВлияет на будущие открытия без explicit context; не посылает запрос, не сбрасывает все настройки через [].
Кто владеет default
  1. Главный скрипт задаёт общую политику
  2. Последующие открытия без context зависят от неё
  3. Сторонняя библиотека не должна менять её молча
  4. Точечная операция выбирает отдельный create

Иногда главный CLI-скрипт сознательно задаёт общую политику до подключения библиотек. set_default подходит владельцу всего процесса, но опасен как незаметный побочный эффект маленькой helper-функции. Настройки дополняются, а не заменяются целиком.

Задать явную политику главного учебного процессаСамостоятельный пример
<?php
declare(strict_types=1);

$default = stream_context_set_default(['http' => ['timeout' => 2, 'follow_location' => 0]]);
var_dump($default === stream_context_get_default());
echo stream_context_get_options($default)['http']['timeout'], "\n";
Ожидаемый результат
bool(true)
2

Это не выполнение HTTP и не глобальная настройка всех PHP-процессов сервера. Изменяется default текущего выполнения. В долгоживущем worker последствия могут затронуть следующую задачу в том же процессе.

Не пытаться очистить default пустым массивомСамостоятельный пример
<?php
declare(strict_types=1);

stream_context_set_default(['http' => ['method' => 'GET', 'timeout' => 2]]);
stream_context_set_default(['http' => ['timeout' => 5]]);
stream_context_set_default([]);
$options = stream_context_get_options(stream_context_get_default())['http'];
echo $options['method'], '/', $options['timeout'], "\n";
Ожидаемый результат
GET/5

Отсутствующие поля не удаляются. Возврат старого массива через setter тоже не является универсальным способом стереть добавленные позже ключи. Для изолированных операций проще явный отдельный context, чем временные глобальные изменения с ложным обещанием полного восстановления.

Связанные операции: stream_context_get_default, stream_context_create, stream_context_get_options.

Маршрут от первого потока к контекстам и фильтрам.

stream_notification_callbackРеализовать свой обработчик уведомлений о работе HTTP/FTP-потока.

PHP 5.2.0+; prototype only · standard

prototype callback(int $notification_code, int $severity, ?string $message, int $message_code, int $bytes_transferred, int $bytes_max): void

Реализовать свой обработчик уведомлений о работе HTTP/FTP-потока.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
notification_codeОдин из STREAM_NOTIFY_* кодов; не путать с severity.
severitySTREAM_NOTIFY_SEVERITY_INFO / WARN / ERR.
message / message_codeНеобязательное описание и wrapper-зависимый код; message nullable. Не сырой доверенный текст для HTML/лога.
bytes_transferred / bytes_maxЗначения применимы к событию; общий размер может быть неизвестен.
Возврат / регистрацияvoid. Это прототип, не встроенная функция: зарегистрируйте свой callable в notification.
ВерсияДокументируется с PHP 5.2; COMPLETED реально поддержан с 8.3.
Событие и состояние отчёта
  1. HTTP wrapper сообщает PROGRESS:128
  2. PHP вызывает вашу функцию с шестью аргументами
  3. Она обновляет выбранное состояние
  4. Основная операция отдельно возвращает данные/отказ

Команда скачивает свой отчёт и хочет показать полученное количество байтов. notification — callback, который wrapper вызывает при событиях. Название stream_notification_callback в мануале описывает прототип: такой встроенной функции нет. Вы создаёте функцию с собственным именем или замыкание и регистрируете её в params.notification.

Отличить описательный прототип от реальной функцииСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(function_exists('stream_notification_callback'));
$events = [];
$notify = function ($code, $severity, $message, $messageCode, $transferred, $maximum) use (&$events) {
    if ($code === STREAM_NOTIFY_PROGRESS) { $events[] = $transferred; }
};
$context = stream_context_create(null, ['notification' => $notify]);
$notify(STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, null, 0, 128, 0);
echo $events[0], " байтов\n";
Ожидаемый результат
bool(false)
128 байтов

Последний вызов $notify(...) — явно смоделированное событие для проверки вашей функции, не свидетельство скачивания. Настоящий wrapper позже использует тот же контракт. use (&$events) позволяет накопить события в выбранном массиве без печати из callback.

Показать прогресс, когда общий размер неизвестенСамостоятельный пример
<?php
declare(strict_types=1);

function progressLabel($received, $total) {
    if ($total <= 0) { return $received . ' байтов, размер неизвестен'; }
    return $received . ' из ' . $total . ' байтов';
}
echo progressLabel(128, 0), "\n";
echo progressLabel(128, 256), "\n";
Ожидаемый результат
128 байтов, размер неизвестен
128 из 256 байтов

Это маленькая функция форматирования данных события, без сети. Не делите автоматически на bytes_max: размер может быть неизвестен, нулевой или ещё уточняться. Прогресс — наблюдение, а не криптографическое подтверждение целостности файла.

Код событияКак интерпретировать
RESOLVE / CONNECTRESOLVE описывает разрешение адреса, но поддержка этого уведомления ещё не реализована по официальной странице констант. CONNECT сообщает установленное соединение.
AUTH_REQUIRED / AUTH_RESULTЗапрос/результат авторизации; не журналируйте секреты.
MIME_TYPE_IS / FILE_SIZE_ISОбъявленные тип/размер; это сведения сервера, не проверка содержимого.
REDIRECTEDНовое направление. Контроль допустимости редиректа должен быть частью HTTP-политики, не одним сообщением прогресса.
PROGRESSПрочитанные байты; maximum может быть неизвестен.
COMPLETEDПоддержка события реализована с PHP 8.3; более ранние версии его никогда не посылали.
FAILUREСообщение об ошибке wrapper; основной результат операции всё равно нужно проверить.

severity принимает STREAM_NOTIFY_SEVERITY_INFO, WARN или ERR. message может быть null, message_code зависит от wrapper. return callback не возвращает скачанные байты и не является общим протоколом «отменить запрос». Порядок/количество вызовов не фиксируют в тесте как одинаковые для FTP, HTTP, сжатия и разных серверов. Настоящий локальный HTTP-тест этой главы отдельно проверяет получение события, не выдаёт прямой вызов функции за сетевое испытание.

Связанные операции: stream_context_create, stream_context_set_params, stream_context_get_params.

Маршрут от первого потока к контекстам и фильтрам.

stream_get_contentsПрочитать оставшиеся байты уже открытого потока в строку, учитывая позицию и память.

PHP 5+ · standard

stream_get_contents(resource $stream, ?int $length = null, int $offset = -1): string|false

Прочитать оставшиеся байты уже открытого потока в строку, учитывая позицию и память.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый читаемый поток.
lengthМаксимум байтов; null — остаток, 0 — пустая строка. Nullable с PHP 8.0. Не число Unicode-знаков.
offsetЕсли ≥0, сначала абсолютный seek; отрицательный — читать с текущей позиции.
Возврат / ошибкиBinary-safe string, включая пустую, либо false при отказе; тип/ресурс TypeError, length<-1 ValueError в PHP 8.5.
ПамятьВесь результат хранится строкой; указанный большой length может сразу резервировать большой буфер.
Путь конкретного документа
  1. pickup.txt содержит номер и две позиции
  2. fgets забирает номер104 и LF
  3. Позиция стоит перед TEA
  4. stream_get_contents возвращает оставшиеся две строки

Файл уже открыт, первые байты прочитаны, и теперь нужен остаток как строка. stream_get_contents работает с этим ресурсом и его текущей позицией. Он не открывает файл по имени заново и не возвращает автоматически ранее прочитанные байты.

Прочитать оставшуюся часть листа выдачиСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/pickup.txt', 'rb');
if ($stream === false) { throw new RuntimeException('Не открыть лист выдачи'); }
try {
    echo 'Номер: ', rtrim(fgets($stream)), "\n";
    $details = stream_get_contents($stream);
    if ($details === false) { throw new RuntimeException('Не прочитать позиции'); }
    echo "Остаток документа:\n", $details;
} finally { fclose($stream); }
Ожидаемый результат
Номер: 104
Остаток документа:
TEA:2
MILK:1

fgets прочитал первую строку вместе с переводом строки. stream_get_contents получает уже открытый ресурс и начинает со следующей позиции. Для чтения всего документа снова нужен rewind; для большого документа — ограниченные порции.

Ограничить чтение и явно перейти к началуСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/catalog.txt', 'rb');
try {
    echo stream_get_contents($stream, 3), "\n";
    echo stream_get_contents($stream, 2), "\n";
    echo stream_get_contents($stream, 3, 0), "\n";
} finally { fclose($stream); }
Ожидаемый результат
Tea
:m
Tea

Первый вызов берёт три байта, второй следующие два, третий сначала seek к offset=0. Сеть/некоторые wrappers не поддерживают seek: offset не делает их произвольно перематываемыми.

Пустота и NUL не являются ошибкойСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://memory', 'w+');
try {
    fwrite($stream, "A\0B"); rewind($stream);
    $bytes = stream_get_contents($stream, 3);
    echo bin2hex($bytes), "\n";
    var_dump(stream_get_contents($stream));
} finally { fclose($stream); }
Ожидаемый результат
410042
string(0) ""

php://memory — поток в памяти. w+ разрешает запись и чтение; rewind возвращает позицию после записи к началу. EOF может дать успешную пустую строку. bin2hex показывает нулевой байт без невидимого символа в результате.

length=null читает оставшееся до конца; с PHP 8.0 это явный nullable параметр. Историческое -1 также означает остаток в PHP 8.5.11, меньше -1 даёт ValueError; для нового кода пишите null. length=0 даёт пустую строку. Отрицательный offset не делает seek; неотрицательный задаёт абсолютную позицию. Неверный ресурс даёт TypeError; невозможное позиционирование/чтение может дать warning и false. Проверяйте === false отдельно от пустой строки.

Связанные операции: stream_get_line, stream_copy_to_stream, stream_get_meta_data.

Маршрут от первого потока к контекстам и фильтрам.

stream_copy_to_streamПеренести байты между открытыми ресурсами и проверить фактически скопированный объём.

PHP 5+ · standard

stream_copy_to_stream(resource $from, resource $to, ?int $length = null, int $offset = 0): int|false

Перенести байты между открытыми ресурсами и проверить фактически скопированный объём.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
from / toОткрытые читаемый источник и записываемый приёмник; ресурсы остаются открытыми после вызова.
lengthМаксимум байтов либо null для остатка; 0 ничего не копирует.
offsetТолько >0 вызывает seek источника к абсолютной позиции. 0/отрицательный не перематывает.
ВозвратЧисло переданных байтов, включая 0, либо false. Короткий успешный результат возможен при EOF.
ОтказыНеверный ресурс TypeError; чтение/запись/seek могут предупредить и вернуть false. Частичная запись не откатывается; socket timeout не равен EOF.
Путь конкретного документа
  1. Открыть pickup.txt для чтения
  2. Открыть archive.txt для записи
  3. Перенести17байт между ресурсами
  4. Закрыть и показать сохранённый archive.txt

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

Скопировать готовый лист выдачи в файл архиваСамостоятельный пример
<?php
declare(strict_types=1);

$from = fopen(__DIR__ . '/pickup.txt', 'rb');
$to = fopen(__DIR__ . '/archive.txt', 'wb');
if ($from === false || $to === false) { throw new RuntimeException('Не открыть файлы'); }
try {
    $copied = stream_copy_to_stream($from, $to);
    if ($copied === false) { throw new RuntimeException('Копирование не завершено'); }
    echo 'Скопировано байтов: ', $copied, "\n";
} finally { fclose($from); fclose($to); }
echo "archive.txt:\n", file_get_contents(__DIR__ . '/archive.txt');
Ожидаемый результат
Скопировано байтов: 17
archive.txt:
104
TEA:2
MILK:1

Первый ресурс — источник, второй — назначение. Без length копируются оставшиеся байты; оба курсора продвигаются. wb создаёт или обнуляет только учебный archive.txt. Если нужны только два имени файлов без фильтров и уже открытых ресурсов, copy проще.

Не спутать offset=0 с перемоткойСамостоятельный пример
<?php
declare(strict_types=1);

$source = fopen(__DIR__ . '/report.txt', 'rb');
$target = fopen('php://temp', 'w+');
try {
    fread($source, 4);
    stream_copy_to_stream($source, $target, 2, 0);
    stream_copy_to_stream($source, $target, 3, 1);
    rewind($target);
    echo stream_get_contents($target), "\n";
} finally { fclose($source); fclose($target); }
Ожидаемый результат
miea:

Здесь offset=0 оставил текущую позицию 4, поэтому скопированы mi. Только положительный offset=1 заставил перейти к e и скопировать ea:. Это отличается от stream_get_contents, где offset=0 означает seek к началу.

Проверить ожидаемый объём фиксированного фрагментаСамостоятельный пример
<?php
declare(strict_types=1);

$source = fopen(__DIR__ . '/report.txt', 'rb');
$target = fopen('php://temp', 'w+');
try {
    $copied = stream_copy_to_stream($source, $target, 10);
    if ($copied === false) { echo "Ошибка копирования\n"; }
    elseif ($copied !== 10) { echo 'Короткий источник: ', $copied, " байта\n"; }
    else { echo "Получен весь ожидаемый фрагмент\n"; }
} finally { fclose($source); fclose($target); }
Ожидаемый результат
Короткий источник: 3 байта

Максимум 10 не означает требование получить ровно 10. Если протокол требует точное число, сравните результат с ожидаемым, а не только с false. Источник заведомо фиксированный и маленький.

length=null — до конца, nullable с PHP 8.0. В PHP 8.5 отрицательные length также фактически ведут к чтению остатка; новый прикладной контракт лучше ограничить null или числом ≥0. offset>0 перемещает только источник и требует seek; offset≤0 оставляет текущую позицию. При отказе часть данных уже могла быть записана: атомарного отката нет. У blocking socket чтение может остановиться по timeout до EOF и вернуть false после частичной записи. Для публикации файла используют отдельный временный результат, проверку и затем согласованную замену, а не объявляют target готовым заранее.

Связанные операции: stream_get_contents, stream_set_timeout, stream_get_meta_data.

Маршрут от первого потока к контекстам и фильтрам.

stream_get_lineПрочитать фрагмент до выбранного разделителя без включения разделителя в результат.

PHP 5+ · standard

stream_get_line(resource $stream, int $length, string $ending = ""): string|false

Прочитать фрагмент до выбранного разделителя без включения разделителя в результат.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый читаемый поток.
lengthМаксимум байтов; отрицательный вызывает ValueError. 0 использует стандартный chunk size (обычно 8192), не безграничное чтение.
endingНепустая строка-разделитель, исключаемая из результата; пустая по умолчанию не ищет специальный разделитель.
ВозвратString до лимита/ending/EOF, что наступит раньше; false при отсутствии дальнейших данных или отказе. Пустой элемент может быть успешной строкой.
ГраницаНе Unicode-разбиение, CSV-парсер или гарантия полной записи. Неверный ресурс TypeError.
Один поток, разные записи
  1. tea|mint
  2. Первое чтение возвращает tea и съедает |
  3. Следующее чтение возвращает mint
  4. EOF завершает маршрут

У старого формата записи разделены вертикальной чертой, а не переводом строки. get_line умеет читать до выбранной непустой строки-разделителя и не включать её в результат. Ограничение length при этом остаётся ограничением байтов одной операции, а не гарантией целой логической записи.

Разобрать две короткие записи через |Самостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/items.txt', 'rb');
try {
    echo stream_get_line($stream, 100, '|'), "\n";
    echo stream_get_line($stream, 100, '|'), "\n";
} finally { fclose($stream); }
Ожидаемый результат
tea
mint

Разделитель прочитан из потока, но не включён в результат. Вторая запись заканчивается EOF. Функция не выполняет полноценный CSV-разбор кавычек и escape-последовательностей.

Отличить пустую запись от конца потокаСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/items.txt', 'rb');
try {
    $items = [];
    while (($line = stream_get_line($stream, 100, '|')) !== false) {
        $items[] = '[' . $line . ']';
    }
    echo implode(',', $items), "\n";
} finally { fclose($stream); }
Ожидаемый результат
[tea],[],[mint]

Пустота между || — настоящая пустая запись. Условие !== false сохраняет её. Это маленький локальный файл; для сокета пустота/EOF/таймаут и ограничение общей работы требуют собственной политики.

Увидеть разрыв записи из-за лимитаСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/items.txt', 'rb');
try {
    echo stream_get_line($stream, 3, '|'), "\n";
    echo stream_get_line($stream, 3, '|'), "\n";
} finally { fclose($stream); }
Ожидаемый результат
abc
def

В файле одна запись abcdef, но лимит вернул её частями. Если нужна запись целиком, приложение копит ограниченные части до разделителя или отвергает превышение; наивный цикл не должен принимать каждую часть за отдельную запись.

Связанные операции: stream_get_contents, stream_get_meta_data.

Маршрут от первого потока к контекстам и фильтрам.

stream_get_meta_dataПолучить диагностическое состояние открытого потока и учитывать различия wrapper.

PHP 4.3.0+ · standard

stream_get_meta_data(resource $stream): array

Получить диагностическое состояние открытого потока и учитывать различия wrapper.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamРесурс от fopen, fsockopen, stream_socket_client и подобных; не Socket object расширения Sockets.
ВозвратМассив сведений, состав зависит от типа и транспорта. Ни чтения полного тела, ни открытия нового соединения нет.
Ошибки / границыЗакрытый/неподходящий ресурс TypeError. Не считайте uri безопасным логом, unread_bytes полной длиной или timed_out общим сетевым deadline.
Путь конкретного документа
  1. php://memory хранит 104:ready
  2. mode сообщает w+b
  3. seekable разрешает попытку перемотки
  4. После rewind прочитан записанный статус

Когда чтение пошло не так, нужны свойства ресурса: поддерживается ли seek, истёк ли timeout, какой wrapper его открыл. get_meta_data смотрит уже существующий поток. Массив различается между файловым, HTTP и TLS потоком; обращаться ко всем возможным ключам без проверки нельзя.

Проверить возможность повторного чтения отчётаСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://memory', 'w+b');
try {
    fwrite($stream, "104:ready\n");
    $info = stream_get_meta_data($stream);
    echo 'Режим: ', $info['mode'], "\n";
    if (!$info['seekable']) { throw new RuntimeException('Нужен новый источник для повторного чтения'); }
    rewind($stream);
    echo stream_get_contents($stream);
} finally { fclose($stream); }
Ожидаемый результат
Режим: w+b
104:ready

Метаданные — массив сведений об открытом потоке. seekable позволяет выбрать повторное чтение; это не гарантия будущего успеха каждой операции. Размер файла смотрят через fstat, не через поле unread_bytes.

Проверить возможность перемотки перед повторным чтениемСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/items.txt', 'rb');
try {
    echo stream_get_contents($stream), "\n";
    $meta = stream_get_meta_data($stream);
    if (!$meta['seekable']) { throw new RuntimeException('Нужен новый источник'); }
    if (fseek($stream, 0) !== 0) { throw new RuntimeException('Перемотка не удалась'); }
    echo stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
tea
tea

seekable описывает возможность; даже после неё проверяется реальный результат fseek. Времена ожидания сети этим файловым примером не измеряются.

КлючЧто известно и чего он не доказывает
timed_outПоследнее поддерживаемое чтение ожидало данные и истекло; не универсальный deadline всех предыдущих операций.
blockedРежим блокирующего ввода-вывода.
eofСостояние EOF; для сокета может сочетаться с unread_bytes>0. Для логики чтения используйте feof и результат read.
unread_bytesВнутренний PHP-буфер. Документация не рекомендует строить на нём логику приложения.
stream_type / wrapper_typeОписание реализации потока / протокольного wrapper; поля могут отсутствовать в конкретном типе.
wrapper_dataПроизвольные данные wrapper, например HTTP headers; это не всегда одинаковый массив.
mode / seekable / uriРежим открытия, поддержка seek и адрес/имя, если эти сведения доступны.
cryptoТолько для TLS: протокол/шифр и другие сведения; отсутствие у обычного потока нормально.

При нескольких HTTP-ответах wrapper_data может содержать redirect chain: первая status line не обязана описывать окончательный ответ. Ресурс, созданный расширением Sockets как объект Socket, сюда не подходит; нужен именно stream (или корректный адаптер). В PHP 8.5.11 неверный/закрытый ресурс приводит к TypeError. Получение и интерпретация HTTP-заголовков разбирает значения ответа отдельно.

Связанные операции: stream_set_timeout, stream_set_blocking, stream_get_contents.

Маршрут от первого потока к контекстам и фильтрам.

stream_resolve_include_pathРазрешить имя своего файла по include_path, не исполняя его.

PHP 5.3.2+ · standard

stream_resolve_include_path(string $filename): string|false

Разрешить имя своего файла по include_path, не исполняя его.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
filenameИмя/путь по правилам fopen/include. Для предсказуемости лучше фиксированное имя разработчика.
ВозвратРазрешённый абсолютный путь как строка либо false, если разрешить не удалось.
ГраницыНе realpath-based sandbox, не проверка прав посетителя и не устранение гонки замены файла. Неправильный тип даёт TypeError.
Находим свой шаблон
  1. include_path содержит templates
  2. Имя receipt.txt ищется по правилам открытия
  3. Получаем абсолютный путь или false
  4. Исполнение/чтение остаётся отдельным действием

Для собственного шаблона известно короткое имя, а каталог выбирается конфигурацией include_path. resolve_include_path позволяет узнать разрешённый путь заранее; она не подключает и не исполняет PHP-файл. Это поиск по правилам открытия, а не контроль доступа к произвольному имени от посетителя.

Найти файл из выбранной папки шаблоновСамостоятельный пример
<?php
declare(strict_types=1);

$before = get_include_path();
try {
    set_include_path(__DIR__ . '/templates');
    $path = stream_resolve_include_path('receipt.txt');
    if ($path === false) { throw new RuntimeException('Шаблон отсутствует'); }
    echo basename($path), '/', file_get_contents($path), "\n";
} finally { set_include_path($before); }
Ожидаемый результат
receipt.txt/Tea receipt

Папку и имя выбрал разработчик. В ответ выводится basename, а не приватный абсолютный путь сервера. include_path возвращён к прежней настройке.

Сообщить об отсутствующем собственном шаблонеСамостоятельный пример
<?php
declare(strict_types=1);

$old = get_include_path();
set_include_path(__DIR__ . '/templates');
try {
    $path = stream_resolve_include_path('missing-receipt.txt');
    echo $path === false ? "Шаблон чека не найден: проверьте комплект файлов.\n" : file_get_contents($path);
} finally { set_include_path($old); }
Ожидаемый результат
Шаблон чека не найден: проверьте комплект файлов.

Функция ищет путь, но не запускает файл. false отделён от содержимого. Имя фиксировано автором сервиса; посетителю нельзя разрешать выбирать подключаемый PHP-файл.

Связанные операции: stream_get_wrappers, stream_is_local.

Маршрут от первого потока к контекстам и фильтрам.

stream_supports_lockУзнать поддержку flock перед выбором стратегии синхронизации.

PHP 5.3.0+ · standard

stream_supports_lock(resource $stream): bool

Узнать поддержку flock перед выбором стратегии синхронизации.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый ресурс потока.
Возвратtrue — механизм поддерживается; false — нет. Это ответ о возможности, не о том, получена ли блокировка.
Ошибки / границыНеверный/закрытый ресурс TypeError. Реальный flock может не получить блокировку даже при true.
Возможность и действие
  1. supports_lock проверяет capability
  2. flock отдельно пытается захватить lock
  3. Только после успеха выполняется защищённая работа
  4. finally освобождает lock

Программа хочет использовать flock и сначала проверяет, умеет ли ресурс этот механизм. supports_lock сообщает возможность, но не захватывает блокировку, не определяет её владельца и не гарантирует успешность будущего flock.

Выбрать возможность совместной записи журналаСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/events.txt', 'c+b');
if ($stream === false) { throw new RuntimeException('Не открыть журнал'); }
try {
    echo stream_supports_lock($stream)
        ? "Поток поддерживает flock; перед записью ещё надо получить блокировку.\n"
        : "Этот поток не подходит для нашего общего журнала.\n";
} finally { fclose($stream); }
Ожидаемый результат
Поток поддерживает flock; перед записью ещё надо получить блокировку.

Это проверка возможности локального ресурса; она не захватывает блокировку. Файл создан в папке примера, а не в общей системной временной папке.

Дополнить общий журнал после настоящей блокировкиСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/events.txt', 'c+b');
if ($stream === false) { throw new RuntimeException('Не открыть журнал'); }
try {
    if (!stream_supports_lock($stream) || !flock($stream, LOCK_EX)) {
        throw new RuntimeException('Нельзя получить блокировку журнала');
    }
    try {
        if (fseek($stream, 0, SEEK_END) !== 0) { throw new RuntimeException('Не найти конец'); }
        $event = "104:ready\n";
        if (fwrite($stream, $event) !== strlen($event) || !fflush($stream)) {
            throw new RuntimeException('Событие не сохранено полностью');
        }
    } finally { flock($stream, LOCK_UN); }
} finally { fclose($stream); }
echo file_get_contents(__DIR__ . '/events.txt');
Ожидаемый результат
104:created
104:ready

c+b не обнуляет существующий журнал. EX удерживается от выбора позиции до fflush; затем освобождается. Читатели и писатели должны соблюдать совместимый протокол. Этот пример завершает ошибкой частичную запись; он не обещает откат уже записанных байтов.

Связанные операции: stream_get_meta_data, stream_is_local.

Маршрут от первого потока к контекстам и фильтрам.

stream_is_localРазличить классификацию local/URL wrapper без ложного обещания безопасности пути.

PHP 5.2.4+ · standard

stream_is_local(resource|string $stream): bool

Различить классификацию local/URL wrapper без ложного обещания безопасности пути.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый ресурс или строка пути/URL; открытие не требуется для строковой проверки.
Возвратtrue для local-классификации, false для URL-классификации; это не универсальный успех/ошибка.
Ошибки / границыНеизвестная схема может предупредить и дать true; неподходящий тип/ресурс TypeError. Не проверяет существование, разрешённый каталог, DNS или права.
Классификация не разрешение
  1. Путь report.txt классифицирован local
  2. Файл может отсутствовать или быть недоступным
  3. Проверка прав и допустимости идёт отдельно
  4. HTTP loopback не превращается в local file

Инструмент выбирает обработку между файловым и URL-wrapper. is_local смотрит категорию wrapper, а не делает DNS, не измеряет расстояние до сервера и не проверяет, что путь безопасен. HTTP на 127.0.0.1 остаётся URL-потоком; data:// тоже не следует считать обычным локальным файлом.

Сравнить файл, память и HTTP без запросовСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(stream_is_local(__DIR__ . '/missing.txt'));
var_dump(stream_is_local('php://temp'));
var_dump(stream_is_local('http://127.0.0.1/report'));
Ожидаемый результат
bool(true)
bool(true)
bool(false)

Файл даже не обязан существовать: проверяется классификация. Адрес HTTP здесь только строка; сетевого соединения нет. true не разрешает читать произвольный путь пользователя.

Не превращать классификатор в whitelist безопасностиСамостоятельный пример
<?php
declare(strict_types=1);

$warning = false;
set_error_handler(function ($level) use (&$warning) {
    if ($level === E_WARNING) { $warning = true; return true; }
    return false;
});
try { $local = stream_is_local('lesson-missing-wrapper://report'); }
finally { restore_error_handler(); }
var_dump($local, $warning);
Ожидаемый результат
bool(true)
bool(true)

В PHP 8.5.11 неизвестная схема может вызвать warning и fallback с true. Поэтому условие if (stream_is_local($input)) не является защитой от опасных схем/путей. Приложение выбирает разрешённые источники явно.

Связанные операции: stream_get_wrappers, stream_resolve_include_path.

Маршрут от первого потока к контекстам и фильтрам.

stream_isattyПроверить терминальное назначение CLI-потока, чтобы не загрязнять перенаправленный вывод.

PHP 7.2.0+ · standard

stream_isatty(resource $stream): bool

Проверить терминальное назначение CLI-потока, чтобы не загрязнять перенаправленный вывод.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый поток, например STDOUT/STDERR в CLI или файл.
Возвратtrue для терминального устройства, false иначе.
Версия / ошибкиPHP 7.2+, работает и в Windows. Неподходящий/закрытый ресурс TypeError; true не подтверждает поддержку ANSI-цветов или желаемый UX.
Назначение вывода определяет формат
  1. Проверяем конкретный stream
  2. TTY допускает выбранный интерактивный режим
  3. Файл получает обычные строки
  4. Сам isatty ничего не печатает

CLI-инструмент может рисовать изменяющийся прогресс только в терминале, а при записи результата в файл должен печатать обычные строки. isatty помогает узнать, является ли конкретный поток терминальным устройством. Это переносимая на Windows альтернатива posix_isatty, но не проверка всех возможностей цвета терминала.

Сохранить чистый отчёт при выводе в файлСамостоятельный пример
<?php
declare(strict_types=1);

$output = fopen(__DIR__ . '/result.txt', 'wb');
if ($output === false) { throw new RuntimeException('Не открыть результат'); }
try {
    if (stream_isatty($output)) { fwrite($output, "Готовим отчёт...\r"); }
    fwrite($output, "104:ready\n");
} finally { fclose($output); }
echo file_get_contents(__DIR__ . '/result.txt');
Ожидаемый результат
104:ready

Обычный файл не является интерактивным терминалом: служебная строка не попала в данные. В CLI такой выбор часто применяют к STDOUT; здесь воспроизводимо показан именно файловый маршрут, без требования открыть терминал.

Выбрать обычные строки для файлового назначенияСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $interactive = stream_isatty($stream);
    fwrite($stream, $interactive ? "\rГотово: 1/2" : "Готово: 1/2\n");
    rewind($stream);
    echo stream_get_contents($stream);
} finally { fclose($stream); }
Ожидаемый результат
Готово: 1/2

В файл попала нормальная строка, а не управляющий курсором прогресс. Реальная интерактивная ветка здесь не тестируется: для неё нужен терминал. Пользовательская настройка отключить анимацию/цвет всё равно должна иметь приоритет.

Связанные операции: stream_get_meta_data, stream_is_local.

Маршрут от первого потока к контекстам и фильтрам.

stream_get_transportsПолучить перечень зарегистрированных транспортов сокетов для выбора поддерживаемого маршрута.

PHP 5+ · standard

stream_get_transports(): array

Получить перечень зарегистрированных транспортов сокетов для выбора поддерживаемого маршрута.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
ВозвратИндексированный массив строковых имён транспорта; набор зависит от сборки, платформы и расширений.
ГраницаНе список wrappers/filters и не проверка успешного соединения/TLS-сертификата.
Возможности окружения
  1. Сборка зарегистрировала tcp/udp/TLS
  2. get_transports возвращает имена
  3. Политика приложения выбирает допустимый transport
  4. Соединение проверяется отдельно

Прежде чем выбирать transport для сокета, узнайте, зарегистрирован ли он в данной сборке PHP. Transports — tcp, udp, tls и подобные механизмы. Это другой список, чем wrappers вроде http или file. Наличие tls ещё не доказывает доступность сети, доверенных сертификатов или совместимость сервера.

Проверить наличие tcp без открытия соединенияСамостоятельный пример
<?php
declare(strict_types=1);

$transports = stream_get_transports();
echo in_array('tcp', $transports, true) ? "TCP зарегистрирован\n" : "TCP отсутствует\n";
Ожидаемый результат
TCP зарегистрирован

Получен список текущей сборки, а не статический универсальный перечень. in_array с true выполняет строгое сравнение имён. Реальный socket client будет отдельным вызовом.

Выбрать только transport из собственной политикиСамостоятельный пример
<?php
declare(strict_types=1);

$allowed = ['tcp', 'udp'];
$available = stream_get_transports();
foreach ($allowed as $name) {
    if (in_array($name, $available, true)) { echo $name, " доступен\n"; }
}
Ожидаемый результат
tcp доступен
udp доступен

Список allowed — решение приложения, available — возможность окружения. Их пересечение не заменяет авторизацию адреса назначения. Unix-domain transport может отсутствовать в Windows; пример не притворяется его проверкой.

Связанные операции: stream_get_wrappers, stream_socket_client, stream_socket_server.

Маршрут от первого потока к контекстам и фильтрам.

stream_get_wrappersУзнать зарегистрированные схемы открытия потоков, отделяя доступность от разрешённости.

PHP 5+ · standard

stream_get_wrappers(): array

Узнать зарегистрированные схемы открытия потоков, отделяя доступность от разрешённости.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
ВозвратИндексированный массив имён зарегистрированных wrappers; меняется расширениями и register/unregister.
ГраницаНаличие не проверяет файл, сервер, доступ, безопасность или allow_url_fopen для конкретного открытия. Не путать с socket transports.
Схема выбирает реализацию
  1. file:// — файловые операции
  2. http:// — HTTP wrapper
  3. php://temp — временное хранение
  4. Список регистрации говорит о наличии, не о правах

Код получил путь вида scheme://target. Имя scheme выбирает wrapper — реализацию открытия и операций. Список зависит от расширений и пользовательских регистраций. Например, http использует HTTP-протокол поверх транспорта, а php://temp реализует временное хранение.

Выбрать доступный встроенный источник отчётаСамостоятельный пример
<?php
declare(strict_types=1);

if (!in_array('php', stream_get_wrappers(), true)) {
    throw new RuntimeException('Нет встроенного php wrapper');
}
$stream = fopen('php://memory', 'w+b');
try { fwrite($stream, "104:ready\n"); rewind($stream); echo stream_get_contents($stream); }
finally { fclose($stream); }
Ожидаемый результат
104:ready

get_wrappers возвращает имена схем. Наличие php разрешает попытку открыть нужный канал, но не гарантирует успех любого адреса. Реестр возможностей не является списком адресов, разрешённых посетителю.

Сделать понятный отказ для незарегистрированной собственной схемыСамостоятельный пример
<?php
declare(strict_types=1);

$scheme = 'lesson-private-catalog';
if (!in_array($scheme, stream_get_wrappers(), true)) {
    echo "Каталог не подключён: wrapper отсутствует\n";
}
Ожидаемый результат
Каталог не подключён: wrapper отсутствует

Это диагностическая проверка выбранной приложением схемы до открытия. Не разрешайте посетителю любой wrapper только потому, что он зарегистрирован; file, php и phar могут предоставлять очень разные возможности.

Связанные операции: stream_wrapper_register, stream_wrapper_unregister, stream_wrapper_restore, stream_get_transports.

Маршрут от первого потока к контекстам и фильтрам.

stream_set_chunk_sizeНастроить внутреннюю порцию потока и сохранить прежнее значение для восстановления.

PHP 5.4.0+ · standard

stream_set_chunk_size(resource $stream, int $size): int

Настроить внутреннюю порцию потока и сохранить прежнее значение для восстановления.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый ресурс потока.
sizeПоложительное количество байтов: от 1 до PHP_INT_MAX. Не лимит полной загрузки.
ВозвратПрежний chunk size, не true/false и не новое значение.
Версии / ошибкиPHP 5.4+. С 8.0 недопустимый размер ValueError вместо warning+false. Тип/ресурс TypeError.
Настройка не граница записи
  1. Был исходный размер порции
  2. Установили 1024 и сохранили прежний
  3. Read по-прежнему задаёт нужный ему объём
  4. Владелец восстанавливает политику

При работе с потоком PHP читает/передаёт данные внутренними порциями. set_chunk_size меняет выбранный размер такой порции и возвращает прежний. Это настройка механизма, а не разбиение вашего документа на гарантированные сообщения ровно по N байтов.

Изменить настройку и проверить возвращённое прежнее значениеСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $original = stream_set_chunk_size($stream, 1024);
    $previous = stream_set_chunk_size($stream, 2048);
    echo $previous, "\n";
    stream_set_chunk_size($stream, $original);
} finally { fclose($stream); }
Ожидаемый результат
1024

Первый прежний размер не закреплён числом в примере: код сохраняет его и восстанавливает. Второй setter вернул именно установленное нами 1024. Это не подсчёт прочитанных байтов.

Размер внутренней порции не заменяет длину readСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/record.txt', 'rb');
try {
    stream_set_chunk_size($stream, 2);
    echo stream_get_contents($stream, 5), "\n";
    echo stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
abcde
fgh

Пользовательский вызов запросил пять байтов, хотя внутренний chunk равен двум. Формат записей/границы сообщений задаются протоколом приложения. Из уменьшения chunk не следует автоматическое ускорение.

Отклонить нулевой размерСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    try { stream_set_chunk_size($stream, 0); }
    catch (ValueError $error) { echo "Chunk должен быть положительным\n"; }
} finally { fclose($stream); }
Ожидаемый результат
Chunk должен быть положительным

С PHP 8.0 недопустимый размер вызывает ValueError; старое описание warning+false для этого случая не подходит современному коду. Ноль здесь не означает отключение буферизации.

Измеряйте настройку на вашем wrapper, размере данных и инфраструктуре. Маленькие порции могут увеличить число вызовов, большие — память/задержку первой порции; универсального быстрого значения нет. Положительное int ограничено PHP_INT_MAX самой платформы. Значение вне диапазона PHP int нельзя безопасно протолкнуть float/string без проверки типов.

Связанные операции: stream_set_read_buffer, stream_get_contents, stream_set_write_buffer.

Маршрут от первого потока к контекстам и фильтрам.

stream_set_read_bufferПопросить wrapper изменить буферизацию чтения и проверить, принял ли он запрос.

PHP 5.3.3+ · standard

stream_set_read_buffer(resource $stream, int $size): int

Попросить wrapper изменить буферизацию чтения и проверить, принял ли он запрос.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый поток.
sizeЧисло буферизуемых байтов; 0 — запрос unbuffered режима. Для приложения используйте неотрицательное проверенное значение.
Возврат0 при успехе; другое число, если запрос нельзя выполнить. Не прежний размер буфера.
Границы / ошибкиНеверный тип/ресурс TypeError. Поддержка wrapper не гарантирована. Не создаёт блокировку, не управляет ob_start и не обещает атомарность между процессами.
Запрос и его исполнение
  1. Приложение просит size=0
  2. Wrapper принимает или отказывает
  3. Код проверяет status===0
  4. Только затем полагается на выбранную политику

Если нужно изменить буферизацию чтения, set_read_buffer передаёт соответствующий запрос реализации потока. Не каждый wrapper его выполняет. Успех — число 0; это особенно важно, потому что обычное if ($result) перевернуло бы смысл проверки.

Запросить небуферизованное чтение и проверить кодСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://memory', 'w+');
try {
    fwrite($stream, 'tea'); rewind($stream);
    $status = stream_set_read_buffer($stream, 0);
    echo $status === 0 ? "Запрос принят\n" : "Wrapper не применил настройку\n";
    echo fread($stream, 3), "\n";
} finally { fclose($stream); }
Ожидаемый результат
Запрос принят
tea

На текущем PHP memory wrapper принимает запрос. Результат проверяется строго с 0. Это не сетевой тест и не обещание одинакового поведения каждого wrapper.

Отделить настройку от логики чтения фиксированного файлаСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen(__DIR__ . '/record.txt', 'rb');
try {
    $status = stream_set_read_buffer($stream, 0);
    if ($status !== 0) { throw new RuntimeException('Этот режим чтения недоступен'); }
    $first = fread($stream, 3);
    $second = fread($stream, 3);
    echo $first, '/', $second, "\n";
} finally { fclose($stream); }
Ожидаемый результат
abc/def

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

size — число байтов; 0 запрашивает отключение буферизации чтения. Формулировку старой документации о порядке чтения процессов нельзя превращать в гарантию взаимного исключения: функция не устанавливает flock и не делает последовательность из нескольких read атомарной. Для координации процессов нужны отдельно выбранные блокировки/протоколы. Поддержка и точный эффект зависят от реализации потока; новые настройки не возвращают уже потреблённые данные.

Связанные операции: stream_set_write_buffer, stream_set_chunk_size, stream_supports_lock.

Маршрут от первого потока к контекстам и фильтрам.

stream_set_write_bufferЗапросить размер буферизации записи, предусмотрев неподдерживаемый wrapper.

PHP 4.3.0+ · standard

stream_set_write_buffer(resource $stream, int $size): int

Запросить размер буферизации записи, предусмотрев неподдерживаемый wrapper.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый поток записи.
sizeКоличество байтов; 0 — запрос unbuffered режима, не блокировки.
Возврат0 при успехе, другое значение при отказе. Не bool и не прежняя настройка.
Ошибки / границыНеверный тип/ресурс TypeError. Эффект зависит от wrapper/платформы; не гарантирует атомарность, полноту fwrite или durability.
Настройка не подтверждение записи
  1. Попросили unbuffered
  2. Получили статус wrapper
  3. Проверили допустимость продолжения
  4. Сам fwrite и его число байтов проверяются отдельно

Программа пишет результат в поток и хочет изменить буферизацию записи. set_write_buffer просит wrapper принять размер, а не отправляет уже накопленные данные и не гарантирует запись на физический носитель. На некоторых ресурсах запрос не поддерживается — это нормальная ветка, которую нужно объяснить вызывающему.

Явно заметить отказ у временного memory wrapperСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://memory', 'w+');
try {
    $status = stream_set_write_buffer($stream, 0);
    echo $status === 0 ? "Запрос принят\n" : "Wrapper не поддерживает настройку\n";
    fwrite($stream, 'receipt');
    rewind($stream);
    echo stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
Wrapper не поддерживает настройку
receipt

В PHP 8.5.11 memory wrapper возвращает ненулевой статус: настройка не применена, но обычная запись работает. Продолжать допустимо лишь когда эта настройка была необязательной оптимизацией, а не требованием вызывающего кода.

Если режим обязателен, не скрывать отказСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    if (stream_set_write_buffer($stream, 0) !== 0) {
        echo "Выберите другой приёмник: нужный режим недоступен\n";
    } else {
        fwrite($stream, 'approved');
        echo "Выбранный режим доступен\n";
    }
} finally { fclose($stream); }
Ожидаемый результат
Выберите другой приёмник: нужный режим недоступен

Сценарий честно проверяет отказ выбранного wrapper. Успешная поддерживаемая ветка дополнительно показана в проекте собственного wrapper, который явно реализует stream_set_option. Не объявляйте каждую запись в temp автоматически unbuffered.

Связанные операции: stream_set_read_buffer, stream_set_chunk_size, stream_supports_lock.

Маршрут от первого потока к контекстам и фильтрам.

stream_set_blockingВыбрать, должен ли поддерживаемый поток ждать данные, и организовать продолжение отдельно.

PHP 4.3.0+ · standard

stream_set_blocking(resource $stream, bool $enable): bool

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

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый поток с поддержкой режима.
enabletrue — blocking, false — non-blocking.
Возвратtrue при успехе, false при отказе; не прежнее состояние.
Ошибки / платформыНеверный/закрытый ресурс TypeError. Локальные файлы Windows не поддерживают non-blocking I/O.
ГраницаПустая строка не всегда EOF. Нет автоматического event loop, timeout или гарантии полного fwrite.
Когда данных ещё нет
  1. Blocking read ждёт согласно своей политике
  2. Non-blocking read возвращается сразу
  3. Приложение сохраняет незавершённую работу
  4. Select/другой механизм сообщает новую готовность

Интеграция ожидает ответ. В blocking-режиме read может ждать данные; в non-blocking он возвращает управление, даже когда сейчас читать нечего. Само переключение не выполняет параллельный код и не превращает приложение в асинхронный фреймворк. Нужно отдельно решить, когда попробовать снова и когда завершить ожидание.

Следующие примеры подключают приложенный tcp-lab.php: он создаёт две стороны настоящего TCP-соединения только на 127.0.0.1 с автоматически выбранным портом. Клиент подключается к нашему временному listener, accept получает вторую сторону, listener закрывается. Тесты не обращаются к интернету. lessonWriteAll / lessonReadExact обрабатывают частичные операции для маленьких сообщений; полный исходник fixture поставляется вместе с примером. Это лаборатория механизма, не готовый многопользовательский сервер.

Не ждать отсутствующих данных, затем прочитать готовыеСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    if (!stream_set_blocking($peer, false)) { throw new RuntimeException('Non-blocking unsupported'); }
    $empty = fread($peer, 16);
    var_dump($empty, feof($peer));
    lessonWriteAll($client, 'tea');
    stream_set_blocking($peer, true);
    echo lessonReadExact($peer, 3), "\n";
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
string(0) ""
bool(false)
tea

Пустая строка до отправки не означала конец соединения: feof=false. Вернули blocking и прочитали известное короткое сообщение. Не запускайте немедленное fread в бесконечном цикле: без ожидания готовности это расходует CPU.

Сохранить и вернуть исходную политику своего ресурсаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    $original = stream_get_meta_data($peer)['blocked'];
    try {
        stream_set_blocking($peer, false);
        var_dump(stream_get_meta_data($peer)['blocked']);
    } finally { stream_set_blocking($peer, $original); }
    var_dump(stream_get_meta_data($peer)['blocked'] === $original);
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
bool(false)
bool(true)

Состояние принадлежит ресурсу, который может быть передан другой функции. У setter bool результата, а не прежний режим, поэтому исходную политику прочитали через metadata. В production проверяют и отказы setter, если выбранный режим обязателен.

enable=true задаёт blocking, false — non-blocking. Поддержка зависит от stream; на Windows non-blocking режим локальных обычных файлов не поддерживается и такая настройка не оказывает на них нужного эффекта. Для сокетов readiness проверяют stream_select. Готовность к записи не гарантирует запись всего сообщения одним fwrite; нужно отслеживать число байтов. После ошибки/пустого чтения проверяют состояние и протокол, не безусловно продолжают цикл.

Связанные операции: stream_select, stream_set_timeout, stream_get_meta_data.

Маршрут от первого потока к контекстам и фильтрам.

stream_set_timeoutОграничить ожидание данных в поддерживаемом потоке и после операции проверить timed_out.

PHP 4.3.0+ · standard

stream_set_timeout(resource $stream, int $seconds, int $microseconds = 0): bool

Ограничить ожидание данных в поддерживаемом потоке и после операции проверить timed_out.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый поток с поддержкой socket timeout.
seconds / microsecondsСекундная и микросекундная части суммарного ожидания. По умолчанию microseconds=0.
Возвратtrue при принятии настройки, false при отказе; это ещё не результат будущего read.
НаблюдениеПосле истечения timed_out в metadata=true, без обязательного предупреждения; соединение может оставаться открытым.
ГраницаНе connect timeout, не общий deadline, не timeout для stream_socket_recvfrom. Неподходящий ресурс TypeError.
Три разных времени
  1. Connect имеет свой timeout
  2. После connect setter задаёт ожидание read/write
  3. Приложение отдельно ограничивает всю задачу
  4. После read metadata различает timeout и обычный результат

У TCP-соединения уже завершён connect, но удалённая сторона может молчать. set_timeout ограничивает поддерживаемые операции ожидания ввода-вывода этого потока. Это не timeout создания соединения и не общий deadline всего импорта, состоящего из многих операций.

Следующие примеры подключают приложенный tcp-lab.php: он создаёт две стороны настоящего TCP-соединения только на 127.0.0.1 с автоматически выбранным портом. Клиент подключается к нашему временному listener, accept получает вторую сторону, listener закрывается. Тесты не обращаются к интернету. lessonWriteAll / lessonReadExact обрабатывают частичные операции для маленьких сообщений; полный исходник fixture поставляется вместе с примером. Это лаборатория механизма, не готовый многопользовательский сервер.

Обнаружить истечение ожидания без warningСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    if (!stream_set_timeout($peer, 0, 20000)) { throw new RuntimeException('Timeout unsupported'); }
    $result = fread($peer, 1);
    $state = stream_get_meta_data($peer);
    echo $state['timed_out'] ? "Ожидание истекло\n" : "Данные получены\n";
    var_dump(feof($peer));
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
Ожидание истекло
bool(false)

Наша вторая сторона ничего не отправляет и остаётся открытой. Чтение ограничено 20 000 микросекундами, но точное реальное время не проверяется: планировщик может задержать возврат. Timeout не означает EOF и сам по себе не создаёт warning.

После контролируемого timeout обработать готовый ответСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    stream_set_timeout($peer, 0, 20000);
    fread($peer, 1);
    if (!stream_get_meta_data($peer)['timed_out']) { throw new RuntimeException('Expected local timeout'); }
    lessonWriteAll($client, 'ok');
    stream_set_timeout($peer, 1);
    echo lessonReadExact($peer, 2), "\n";
    var_dump(stream_get_meta_data($peer)['timed_out']);
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
ok
bool(false)

Повтор здесь безопасен: лаборатория читает уже отправленные байты и не дублирует бизнес-запрос. Для банковского HTTP-запроса истёкшее ожидание не разрешает слепо повторить создание платежа — исход мог остаться неизвестным.

Суммарное время задаётся seconds + microseconds. Принимайте проверенные неотрицательные значения; timeout относится к поддерживаемому stream I/O. Для stream_socket_recvfrom этот setter не работает как обещание предельного ожидания: используйте stream_select с timeout. Исторические имена set_socket_timeout / socket_set_timeout рассматривайте в их отдельных alias-карточках, новый код использует stream_set_timeout.

Связанные операции: stream_get_meta_data, stream_select, stream_socket_client.

Маршрут от первого потока к контекстам и фильтрам.

stream_selectДождаться готовности потоков и обработать изменённые наборы без предположения о полной передаче.

PHP 4.3.0+ · standard

stream_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, ?int $microseconds = null): int|false

Дождаться готовности потоков и обработать изменённые наборы без предположения о полной передаче.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
read / write / exceptМассивы streams либо переменные null, передаваемые по ссылке и изменяемые функцией.
seconds / microsecondsТаймаут; seconds=null — без определённого конца, 0/0 — опрос. microseconds nullable с 8.1.
ВозвратЧисло готовых ресурсов (0 допустим) либо false с warning при системном отказе.
Ошибки / ограниченияTypeError для неправильных типов, ValueError для недопустимых наборов/времени; platform/wrapper ограничения; EOF тоже готовность.
Особый путьPHP-buffered read-ready возвращается без системного select; write/except очищаются, не проверяясь.
Ожидаем готовность, не готовый документ
  1. read содержит два ожидаемых потока
  2. Select ждёт событие или timeout
  3. Read-array остаётся только с готовыми ресурсами
  4. Read проверяет реальные байты/EOF и обновляет состояние

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

Следующие примеры подключают приложенный tcp-lab.php: он создаёт две стороны настоящего TCP-соединения только на 127.0.0.1 с автоматически выбранным портом. Клиент подключается к нашему временному listener, accept получает вторую сторону, listener закрывается. Тесты не обращаются к интернету. lessonWriteAll / lessonReadExact обрабатывают частичные операции для маленьких сообщений; полный исходник fixture поставляется вместе с примером. Это лаборатория механизма, не готовый многопользовательский сервер.

Сначала пустая очередь, затем готовый потокСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    $read = ['reply' => $peer]; $write = null; $except = null;
    echo stream_select($read, $write, $except, 0), "\n";
    lessonWriteAll($client, 'tea');
    $read = ['reply' => $peer]; $write = null; $except = null;
    $ready = stream_select($read, $write, $except, 1);
    if ($ready === false || $ready === 0) { throw new RuntimeException('Local reply not ready'); }
    echo array_key_first($read), '/', lessonReadExact($read['reply'], 3), "\n";
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
0
reply/tea

Массивы передаются по ссылке: результат первого вызова опустошил $read. Перед следующим ожиданием он создан заново. Именованный ключ reply сохранился. Нулевой timeout используется один раз для наблюдения, не в горячем бесконечном цикле.

Готовность может означать EOF, а не полезные байтыСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    stream_socket_shutdown($client, STREAM_SHUT_WR);
    $read = [$peer]; $write = null; $except = null;
    $ready = stream_select($read, $write, $except, 1);
    if ($ready === false || $ready === 0) { throw new RuntimeException('EOF not ready'); }
    var_dump(fread($peer, 1), feof($peer));
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
string(0) ""
bool(true)

Закрытие направления записи дало противоположной стороне EOF. Чтение без блокировки возвращает пустую строку — именно поэтому select считает такой ресурс готовым. В рабочем event loop его удаляют из набора ожидания, иначе получится постоянная готовность.

Учесть приоритет уже накопленного PHP-буфераСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    lessonWriteAll($client, 'abcd');
    echo fread($peer, 1), "\n";
    $read = [$peer]; $write = [$client]; $except = null;
    $count = stream_select($read, $write, $except, 0);
    echo $count, '/', count($read), '/', count($write), "\n";
    echo lessonReadExact($peer, 3), "\n";
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
a
1/1/0
bcd

В проверенной локальной передаче после первого байта оставшиеся данные уже в буфере PHP. Select возвращает read-ready без системного select, а write/except очищает без проверки. Нельзя сделать вывод «клиент не умеет писать» по пустому write в этом частном пути.

Аргумент/ситуацияКонтракт
&$readЧтение не заблокируется; в том числе при EOF. После вызова остаются готовые ресурсы, исходные ключи сохранены.
&$writeЗапись некоторого количества байтов не должна блокироваться; не обещание полной отправки.
&$exceptИсключительные/OOB данные, не универсальный массив всех PHP-ошибок.
seconds / microsecondsСуммарный timeout. 0 + 0/null — немедленная проверка; seconds=null допускает неограниченное ожидание. Nullable microseconds с PHP 8.1.
ReturnКоличество готовых ресурсов в изменённых массивах; 0 — истёк timeout, false — ошибка с warning. Используйте === false.
Недопустимые аргументыНапример, все наборы пустые или отрицательное ожидание в современном PHP дают ValueError. Ссылочные аргументы должны быть переменными, не литералом null.

Сигнал ОС способен прервать системное ожидание и привести к false. Некоторые streams, например zlib, не имеют подходящего дескриптора. На Windows proc_open pipes не поддерживаются stream_select, а готовность консольного STDIN по событию ещё не гарантирует неблокирующий ввод строки. Если данные уже во внутреннем PHP-буфере, применяется описанный быстрый путь: write/except в этот раз не проверяются. Универсальный event loop должен учитывать эти ограничения, EOF, неполные read/write и собственный deadline.

Связанные операции: stream_set_blocking, stream_set_timeout, stream_get_meta_data, stream_socket_recvfrom.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_serverСоздать локальный адрес для входящих соединений, отделяя listener от принятого клиента.

PHP 5+ · standard

stream_socket_server(string $address, int &$error_code = null, string &$error_message = null, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, ?resource $context = null): resource|false

Создать локальный адрес для входящих соединений, отделяя listener от принятого клиента.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
addresstransport://target: Internet host:port, IPv6 в [], Unix path при поддержке.
error_code / error_messageВыходные переменные по ссылке для ошибки ОС; 0 вместе с false не успех.
flagsBIND обязателен для server; LISTEN для TCP-подобных, не для UDP.
contextРесурс настроек или null; nullable с PHP 8.0.
Возврат / ошибкиРесурс listener/datagram socket либо false с warning при отказе. Типы/ресурсы проверяются; stream_socket_accept нужен отдельно для TCP.
Три участника локальной связи
  1. Server слушает 127.0.0.1:свободный порт
  2. Client подключается к этому адресу
  3. Accept создаёт отдельный peer
  4. Server остаётся для приёма следующих соединений

Для локального учебного сервиса нужен адрес, к которому сможет подключиться клиент. server создаёт слушающий ресурс; входящего клиента он ещё не принимает. В примерах выбираем только loopback 127.0.0.1 и порт 0 — ОС выделит свободный порт, поэтому не занимаем заранее известный порт приложения.

Создать и закрыть локальный listenerСамостоятельный пример
<?php
declare(strict_types=1);

$server = stream_socket_server('tcp://127.0.0.1:0', $errorCode, $errorMessage);
if ($server === false) { throw new RuntimeException('Listener не создан'); }
try {
    $address = stream_socket_get_name($server, false);
    echo str_starts_with($address, '127.0.0.1:') ? "Слушаем только loopback\n" : "Иной адрес\n";
} finally { fclose($server); }
Ожидаемый результат
Слушаем только loopback

Это настоящий локальный сокет; сетевых запросов к внешним сервисам нет. Объявленный порт непредсказуем, поэтому проверяется его адресная область, а не конкретное число. Закрытие освобождает listener.

Связать listener, client и accepted peerСамостоятельный пример
<?php
declare(strict_types=1);

$server = stream_socket_server('tcp://127.0.0.1:0', $errorCode, $errorMessage);
if ($server === false) { throw new RuntimeException('Нет listener'); }
$client = false; $peer = false;
try {
    $address = stream_socket_get_name($server, false);
    $client = stream_socket_client('tcp://' . $address, $errorCode, $errorMessage, 1);
    if ($client === false) { throw new RuntimeException('Нет client'); }
    $peer = stream_socket_accept($server, 1);
    if ($peer === false) { throw new RuntimeException('Нет peer'); }
    echo ($server !== $peer && $client !== $peer) ? "Три разных ресурса\n" : "Ошибка модели\n";
} finally {
    if (is_resource($peer)) { fclose($peer); }
    if (is_resource($client)) { fclose($client); }
    fclose($server);
}
Ожидаемый результат
Три разных ресурса

Listener продолжает представлять адрес приёма новых соединений; peer — серверная сторона именно принятого клиента. Client — другая сторона той же связи. Прикладные данные читают из peer, не из listener.

Адрес / флагКак выбирать
tcp://host:portОбычно BIND | LISTEN. IP IPv6 записывается в [], например tcp://[::1]:0.
udp://host:portТолько STREAM_SERVER_BIND: UDP не слушает очередь соединений, accept не используется.
unix://pathПуть Unix-domain socket; доступность зависит от платформы/сборки, проверяйте transports.
STREAM_SERVER_BINDСвязать ресурс с адресом; нужен для server.
STREAM_SERVER_LISTENПринимать connection-oriented соединения, например TCP. Для UDP ошибочен.

error_code и error_message заполняются по ссылке при отказах socket/bind/listen. false при error_code=0 может означать отказ до системного bind; не воспринимайте 0 как успех без ресурса. Сообщение ОС не выводят посетителю целиком. context задаёт параметры socket/TLS; null допустим с PHP 8.0. Порт ниже 1024 на многих системах требует дополнительных прав; учебный ephemeral port этого не требует. Открытие на 0.0.0.0 или :: расширило бы доступ за пределы loopback — не подменяйте адрес без понимания назначения.

Связанные операции: stream_socket_client, stream_socket_accept, stream_socket_recvfrom, stream_get_transports.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_clientОткрыть клиентский stream к выбранному адресу и отдельно вести протокол обмена.

PHP 5+ · standard

stream_socket_client(string $address, int &$error_code = null, string &$error_message = null, ?float $timeout = null, int $flags = STREAM_CLIENT_CONNECT, ?resource $context = null): resource|false

Открыть клиентский stream к выбранному адресу и отдельно вести протокол обмена.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
addresstransport://target, для TCP/UDP host:port; IPv6 в [], Unix path при поддержке.
error_code / error_messageВыходные переменные по ссылке. false при нулевом коде может означать ошибку до connect.
timeoutТолько синхронный connect; null — default_socket_timeout. Не timeout всего запроса.
flagsCONNECT; дополнительно ASYNC_CONNECT и/или PERSISTENT с отдельными условиями.
contextРесурс контекста или null, nullable с PHP 8.0.
Возврат / ошибкиОткрытый stream либо false с предупреждением. Успех UDP/open/persistent не гарантирует готовый сервис или успешный бизнес-ответ.
Открытие и протокол
  1. Client открывает транспортную связь
  2. Программа пишет команду
  3. Серверная сторона читает байты
  4. Программа проверяет ответ, время и состояние

Сервису нужен клиентский канал к известному серверу. socket_client открывает транспортное соединение; HTTP-запрос, JSON и бизнес-протокол он сам не создаёт. Для обычного HTTP API часто удобнее готовый HTTP-клиент; ниже изучаем механизм на коротком локальном обмене.

Следующие примеры подключают приложенный tcp-lab.php: он создаёт две стороны настоящего TCP-соединения только на 127.0.0.1 с автоматически выбранным портом. Клиент подключается к нашему временному listener, accept получает вторую сторону, listener закрывается. Тесты не обращаются к интернету. lessonWriteAll / lessonReadExact обрабатывают частичные операции для маленьких сообщений; полный исходник fixture поставляется вместе с примером. Это лаборатория механизма, не готовый многопользовательский сервер.

Отправить команду своей второй сторонеСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    lessonWriteAll($client, "PING\n");
    echo lessonReadExact($peer, 5);
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
PING

Fixture открывает client через stream_socket_client на локальный listener. Полезные данные отправляются отдельным fwrite внутри helper; открытие само не передаёт PING. TCP — поток байтов, границу команды здесь задаёт перевод строки и ожидаемая длина.

Обработать отказ неизвестного транспорта без внешней сетиСамостоятельный пример
<?php
declare(strict_types=1);

$warned = false;
set_error_handler(function ($level) use (&$warned) {
    if ($level === E_WARNING) { $warned = true; return true; }
    return false;
});
try {
    $client = stream_socket_client('lesson-unknown-transport://127.0.0.1:1', $code, $message, 0.1);
} finally { restore_error_handler(); }
var_dump($client === false, $warned);
echo "Пользователю: соединение недоступно\n";
Ожидаемый результат
bool(true)
bool(true)
Пользователю: соединение недоступно

Выбрана заведомо незарегистрированная учебная схема: реальный сервер не вызывается. Ошибки ОС/внутренние адреса не публикуются. Код не принимает error_code=0 за подтверждение успеха: главным условием остаётся ресурс либо false.

По умолчанию соединение открывается blocking. timeout ограничивает connect при синхронной попытке, null берёт default_socket_timeout; чтение/запись на уже созданном сокете имеют другую настройку stream_set_timeout. С PHP 8.0 timeout и context nullable. Числовой IPv6 требует квадратных скобок. UDP «соединение» может успешно создаться даже при недоступном адресате: без рукопожатия это не проверка получателя. Unix-domain и некоторые виды connect timeout зависят от окружения.

ФлагКонтракт и ограничение
STREAM_CLIENT_CONNECTОсновной флаг открытия; должен входить в комбинацию.
STREAM_CLIENT_ASYNC_CONNECTДополняет CONNECT: запуск без синхронного ожидания. Переданный connect timeout к такому ожиданию не применяется. Готовность к записи ещё не доказывает успех соединения: системная ошибка должна быть проверена выбранным API/адаптером, например SO_ERROR при подходящем socket API.
STREAM_CLIENT_PERSISTENTСохранять соединение для повторного использования в обслуживающем процессе между запросами. Это не одна общая связь всех workers и не гарантия живого/чистого протокольного состояния.

Связанные операции: stream_socket_server, stream_set_timeout, stream_set_blocking, stream_select, stream_socket_enable_crypto.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_acceptПринять одну входящую TCP-подобную связь и получить отдельный ресурс клиента.

PHP 5+ · standard

stream_socket_accept(resource $socket, ?float $timeout = null, string &$peer_name = null): resource|false

Принять одну входящую TCP-подобную связь и получить отдельный ресурс клиента.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
socketСлушающий resource от stream_socket_server.
timeoutСекунды ожидания; null — default_socket_timeout. Nullable с PHP 8.0.
peer_nameВыходная переменная адреса клиента, если доступно для транспорта; не удостоверение личности.
Возврат / ошибкиРесурс принятой связи либо false при отказе/timeout, обычно с warning; неправильный ресурс TypeError.
UDPНе использовать accept: datagrams читают recvfrom и отправляют sendto.
Listener и его клиент
  1. Server держит адрес приёма
  2. Accept ожидает один входящий connect
  3. Возвращает отдельный peer resource
  4. Данные читаются из peer, listener остаётся

Listener уже открыт, но у входящего клиента должен появиться отдельный ресурс. accept ждёт и возвращает именно принятую связь. Она живёт отдельно от listener: закрытие одного клиента не обязано прекращать приём остальных.

Получить ресурс и адрес принятого клиентаСамостоятельный пример
<?php
declare(strict_types=1);

$server = stream_socket_server('tcp://127.0.0.1:0', $code, $message);
$client = false; $peer = false;
try {
    $address = stream_socket_get_name($server, false);
    $client = stream_socket_client('tcp://' . $address, $code, $message, 1);
    $peer = stream_socket_accept($server, 1, $peerName);
    if ($peer === false) { throw new RuntimeException('Accept failed'); }
    var_dump($peerName === stream_socket_get_name($client, false));
} finally {
    if (is_resource($peer)) { fclose($peer); }
    if (is_resource($client)) { fclose($client); }
    if (is_resource($server)) { fclose($server); }
}
Ожидаемый результат
bool(true)

peerName — сетевой адрес клиентской стороны, не имя авторизованного пользователя. Переменная заполняется по ссылке; заранее заданного значения ей не нужно. Сравнение избегает вывода непредсказуемого номера порта.

Завершить ожидание, если никто не подключилсяСамостоятельный пример
<?php
declare(strict_types=1);

$server = stream_socket_server('tcp://127.0.0.1:0', $code, $message);
if ($server === false) { throw new RuntimeException('No listener'); }
$warning = false;
set_error_handler(function ($level) use (&$warning) {
    if ($level === E_WARNING) { $warning = true; return true; }
    return false;
});
try { $peer = stream_socket_accept($server, 0.02); }
finally { restore_error_handler(); fclose($server); }
var_dump($peer === false, $warning);
Ожидаемый результат
bool(true)
bool(true)

В собственный listener никто не подключается, ожидание ограничено 20 миллисекундами. Проверяются исход и warning, не точное время планировщика. Серверный цикл должен отличать обычное завершение ожидания от необходимости остановить приложение.

Связанные операции: stream_socket_server, stream_socket_get_name, stream_socket_recvfrom.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_get_nameПолучить локальное или удалённое имя существующего socket stream.

PHP 5+ · standard

stream_socket_get_name(resource $socket, bool $remote): string|false

Получить локальное или удалённое имя существующего socket stream.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
socketSocket stream, не объект Socket другого расширения.
remotetrue — противоположная сторона, false — локальная сторона ресурса.
ВозвратСтрока имени/адреса либо false, если получить его нельзя. Формат зависит от транспорта.
ГраницаНе обратный DNS, не проверка авторизации и не универсальный парсер host/port. Неверный/закрытый ресурс TypeError.
Адрес зависит от стороны
  1. Client local — его исходящий порт
  2. Client remote — порт сервера
  3. Peer local совпадает с client remote
  4. Peer remote совпадает с client local

У соединения две стороны. get_name(false) описывает локальный адрес данного ресурса, get_name(true) — противоположный. У client и accepted peer роли меняются местами; это помогает проверить свою лабораторию и не перепутать адрес прослушивания с адресом клиента.

Следующие примеры подключают приложенный tcp-lab.php: он создаёт две стороны настоящего TCP-соединения только на 127.0.0.1 с автоматически выбранным портом. Клиент подключается к нашему временному listener, accept получает вторую сторону, listener закрывается. Тесты не обращаются к интернету. lessonWriteAll / lessonReadExact обрабатывают частичные операции для маленьких сообщений; полный исходник fixture поставляется вместе с примером. Это лаборатория механизма, не готовый многопользовательский сервер.

Сопоставить две стороны связиСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    $clientRemote = stream_socket_get_name($client, true);
    $serverLocal = stream_socket_get_name($peer, false);
    var_dump($clientRemote === $serverLocal);
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
bool(true)

Удалённый адрес client совпал с локальным адресом принятого peer. Функция возвращает строковое сетевое имя, не преобразует её в пользовательскую учётную запись.

Проверить обратное направлениеСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client, $peer] = lessonTcpPair();
try {
    $clientLocal = stream_socket_get_name($client, false);
    $serverRemote = stream_socket_get_name($peer, true);
    var_dump($clientLocal === $serverRemote);
    echo str_starts_with($serverRemote, '127.0.0.1:') ? "Учебный локальный клиент\n" : "Иной адрес\n";
} finally { fclose($client); fclose($peer); }
Ожидаемый результат
bool(true)
Учебный локальный клиент

Содержимое зависит от транспорта и ОС; не извлекайте порт наивным explode по каждому двоеточию: IPv6 содержит собственные двоеточия. В логах адреса могут быть чувствительными метаданными.

Связанные операции: stream_socket_client, stream_socket_accept, stream_get_meta_data.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_pairСоздать два соединённых socket stream для локального двустороннего обмена.

PHP 5.1.0+ · standard

stream_socket_pair(int $domain, int $type, int $protocol): array|false

Создать два соединённых socket stream для локального двустороннего обмена.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
domain / type / protocolСемейство адресов, тип обмена и протокол по STREAM_* константам; поддержка зависит от ОС.
ВозвратМассив двух ресурсов [0,1] либо false с warning при отказе.
ГраницаНе создаёт процессы и не определяет framing сообщений. Оба конца нужно закрыть; IPC/fork — отдельный жизненный цикл.
Два конца, одна связь
  1. Pair создаёт ресурс 0 и ресурс 1
  2. Запись в 0 читается из 1
  3. Ответ из 1 читается из 0
  4. Каждый владелец закрывает свои ресурсы

Для обмена между двумя участниками иногда не нужен видимый listener. socket_pair создаёт сразу два соединённых сокетных ресурса. Они равноправны: данные, записанные в первый, читает второй, и наоборот. Типичное применение — IPC, но первый опыт можно провести в одном процессе без fork.

Передать сообщение второй стороне парыСамостоятельный пример
<?php
declare(strict_types=1);

$family = PHP_OS_FAMILY === 'Windows' ? STREAM_PF_INET : STREAM_PF_UNIX;
$pair = stream_socket_pair($family, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
if ($pair === false) { throw new RuntimeException('Socket pair unavailable'); }
try {
    stream_set_timeout($pair[1], 1);
    if (fwrite($pair[0], 'tea') !== 3) { throw new RuntimeException('Short fixture write'); }
    echo fread($pair[1], 3), "\n";
} finally { fclose($pair[0]); fclose($pair[1]); }
Ожидаемый результат
tea

Для проверенного Windows runtime выбрано INET; на Unix-подобном окружении обычно используют UNIX. Наличие константы ещё не доказывает поддержку каждой комбинации family/type/protocol. Нет создания второго процесса: это только модель двух концов.

Вернуть короткое подтверждение по той же пареСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
$family = PHP_OS_FAMILY === 'Windows' ? STREAM_PF_INET : STREAM_PF_UNIX;
$pair = stream_socket_pair($family, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
if ($pair === false) { throw new RuntimeException('Socket pair unavailable'); }
try {
    stream_set_timeout($pair[0], 1); stream_set_timeout($pair[1], 1);
    lessonWriteAll($pair[0], 'PING');
    $request = lessonReadExact($pair[1], 4);
    lessonWriteAll($pair[1], $request === 'PING' ? 'PONG' : 'FAIL');
    echo lessonReadExact($pair[0], 4), "\n";
} finally { fclose($pair[0]); fclose($pair[1]); }
Ожидаемый результат
PONG

Здесь fixture даёт только helpers для частичных read/write; listener не создаётся. В настоящем fork каждая сторона закрывает неиспользуемый конец, иначе EOF может не прийти. pcntl/fork не требуется и не заявляется проверенным в Windows.

ПараметрДокументированные семейства значений
domainSTREAM_PF_INET, STREAM_PF_INET6, STREAM_PF_UNIX; доступность и поддержка ОС различаются.
typeSTREAM_SOCK_STREAM, DGRAM, RAW, RDM, SEQPACKET; не каждая комбинация реализована, RAW может требовать специальных прав.
protocolSTREAM_IPPROTO_IP, ICMP, RAW, TCP, UDP — выбрать согласованно с family/type, а не произвольное число.

Связанные операции: stream_socket_client, stream_socket_shutdown, stream_select.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_shutdownЗавершить выбранное направление full-duplex связи, отделяя это от закрытия ресурса.

PHP 5.2.1+ · standard

stream_socket_shutdown(resource $stream, int $mode): bool

Завершить выбранное направление full-duplex связи, отделяя это от закрытия ресурса.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый socket stream.
modeSTREAM_SHUT_RD — дальнейший приём; WR — передача; RDWR — оба направления.
Возвратtrue при успехе, false при отказе.
ГраницыРесурс остаётся для fclose. Буферы могут быть опустошены или нет; не гарантия доставки/бизнес-успеха. Неверный ресурс TypeError; недопустимый mode нужно исключить контрактом приложения.
Полузакрытый запрос
  1. Client отправил report
  2. SHUT_WR завершил только его запись
  3. Peer видит EOF и отправляет ACK
  4. Client ещё может прочитать ACK

Иногда команда завершила отправку тела, но ещё ждёт ответ по тому же соединению. Shutdown направления записи сообщает противоположной стороне EOF после переданных байтов, сохраняя возможность читать ответ. Это отличается от fclose, который закрывает ресурс целиком.

Закончить запрос и прочитать подтверждение по той же связиСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client,$peer]=lessonTcpPair();
try {
    lessonWriteAll($client,'report');
    if(!stream_socket_shutdown($client,STREAM_SHUT_WR)){throw new RuntimeException('Half-close failed');}
    echo stream_get_contents($peer), "\n";
    lessonWriteAll($peer,'ACK');
    echo lessonReadExact($client,3), "\n";
} finally {fclose($client);fclose($peer);}
Ожидаемый результат
report
ACK

Peer дочитал до EOF, потому что client закрыл только передачу. Обратное направление ещё работает. В реальном протоколе EOF-фрейминг должен быть согласован: HTTP/TLS могут иметь собственные правила завершения.

Отключить направления и отдельно закрыть ресурсСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tcp-lab.php';
[$client,$peer]=lessonTcpPair();
try {
    var_dump(stream_socket_shutdown($client,STREAM_SHUT_RDWR));
    var_dump(is_resource($client));
} finally {fclose($client);fclose($peer);}
var_dump(is_resource($client));
Ожидаемый результат
bool(true)
bool(true)
bool(false)

Успешный shutdown не уничтожает PHP-ресурс: fclose всё равно нужен. Состояние связанных буферов не имеет универсального обещания «всё было доставлено»; функция не является подтверждением принятия сообщения приложением на другом конце.

Связанные операции: stream_socket_client, stream_get_contents, stream_select.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_recvfromПолучить байты непосредственно из сокета, при необходимости вместе с адресом отправителя.

PHP 5+ · standard

stream_socket_recvfrom(resource $socket, int $length, int $flags = 0, ?string &$address = null): string|false

Получить байты непосредственно из сокета, при необходимости вместе с адресом отправителя.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
socketSocket stream, соединённый или нет.
lengthМаксимум принимаемых байтов; прикладная политика должна задать допустимый положительный предел. Для UDP недостаточный предел способен отбросить хвост сообщения.
flags0 обычно; STREAM_PEEK — не потреблять, STREAM_OOB — внеполосные данные при поддержке транспорта. Можно сочетать поддерживаемые биты.
addressВыходная переменная адреса другой стороны, если получен; не доказательство доверия.
Возврат / ошибкиBinary string либо false при отказе; пустые данные интерпретируются по транспорту/протоколу. Неверный ресурс TypeError, системный отказ может предупредить.
Граница datagram
  1. Отправитель передал abcdef одной datagram
  2. Receiver выделил только 3 байта
  3. В Windows-опыте получил false
  4. Остаток не остался для следующего read

UDP-обработчик получает отдельную датаграмму и адрес отправителя, чтобы ответить ему. recvfrom читает из сокета напрямую; это не то же самое, что чтение уже накопленного PHP-буфера. Максимум length должен соответствовать вашему протоколу, иначе хвост datagram может потеряться.

udp-lab.php создаёт два настоящих UDP-сокета только на 127.0.0.1, каждый на автоматически выбранном порту. Отправка выполняется локально; перед recvfrom helper ждёт read-ready через stream_select с лимитом 1 секунда. Это маленькие учебные datagrams, не проверка надёжности произвольной сети.

Получить сообщение и его адрес отправителяСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/udp-lab.php';
[$sender,$receiver,$address]=lessonUdpPair();
try {
    if(stream_socket_sendto($sender,'tea',0,$address)!==3){throw new RuntimeException('Send failed');}
    lessonWaitRead($receiver);
    $data=stream_socket_recvfrom($receiver,64,0,$from);
    echo $data, "\n";
    var_dump($from===stream_socket_get_name($sender,false));
} finally {fclose($sender);fclose($receiver);}
Ожидаемый результат
tea
bool(true)

Адрес получен по ссылке в $from. Он пригоден для ответного маршрута, но не удостоверяет личность пользователя. Подлинность и защита от подделки/повтора относятся к протоколу.

Посмотреть сообщение, затем потребить егоСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/udp-lab.php';
[$sender,$receiver,$address]=lessonUdpPair();
try {
    stream_socket_sendto($sender,'tea',0,$address);
    lessonWaitRead($receiver);
    echo stream_socket_recvfrom($receiver,64,STREAM_PEEK), "\n";
    echo stream_socket_recvfrom($receiver,64), "\n";
} finally {fclose($sender);fclose($receiver);}
Ожидаемый результат
tea
tea

PEEK не извлёк данные из очереди сокета. Второй recvfrom прочитал ту же datagram. Нельзя бесконечно делать peek и считать очередь обработанной.

Увидеть потерю хвоста слишком длинной UDP-datagramСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/udp-lab.php';
[$sender,$receiver,$address]=lessonUdpPair();
try {
    stream_socket_sendto($sender,'abcdef',0,$address);
    lessonWaitRead($receiver);
    $data=stream_socket_recvfrom($receiver,3);
    if($data===false || strlen($data)!==6){echo "Целое сообщение не получено\n";}
    $read=[$receiver];$write=null;$except=null;
    echo stream_select($read,$write,$except,0), "\n";
} finally {fclose($sender);fclose($receiver);}
Ожидаемый результат
Целое сообщение не получено
0

В PHP 8.5.11 на Windows слишком маленькое чтение этой UDP-datagram вернуло false, и в очереди больше не осталось её продолжения. Документация допускает потерю лишних байтов; не обещайте одинаковую форму частичного результата на всех ОС. Нельзя переносить привычку TCP «дочитать остаток следующим вызовом».

Связанные операции: stream_socket_sendto, stream_select, stream_set_timeout.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_sendtoПередать сообщение выбранному socket-адресату и проверить число отправленных байтов.

PHP 5+ · standard

stream_socket_sendto(resource $socket, string $data, int $flags = 0, string $address = ""): int|false

Передать сообщение выбранному socket-адресату и проверить число отправленных байтов.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
socketСоединённый или несоединённый socket stream.
dataСтрока байтов сообщения; кодировка/формат согласуются протоколом.
flags0 либо поддерживаемый STREAM_OOB. STREAM_PEEK предназначен только для чтения.
addressПустая строка — адрес соединения; альтернатива числовой IPv4:port / [IPv6]:port.
ВозвратЧисло отправленных байтов либо false при отказе; проверяйте полноту и отдельный ответ, если он нужен.
ГраницаНе доставка UDP, не подтверждение бизнес-операции. Неверный ресурс/типы TypeError; сетевой отказ может предупредить.
Отправка и подтверждение разные
  1. sendto передал PING локальному транспорту
  2. Receiver действительно получил datagram
  3. Он отдельно отправил PONG
  4. Только прикладной ответ подтверждает выбранный этап

Для UDP-ответа нужен явный адрес, полученный при приёме. sendto передаёт bytes в выбранный сокетный транспорт. Число записанных байтов подтверждает работу локальной операции отправки, но не получение сообщения удалённым приложением.

udp-lab.php создаёт два настоящих UDP-сокета только на 127.0.0.1, каждый на автоматически выбранном порту. Отправка выполняется локально; перед recvfrom helper ждёт read-ready через stream_select с лимитом 1 секунда. Это маленькие учебные datagrams, не проверка надёжности произвольной сети.

Передать одну датаграмму известному локальному адресатуСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/udp-lab.php';
[$sender,$receiver,$address]=lessonUdpPair();
try {
    $sent=stream_socket_sendto($sender,'tea',0,$address);
    if($sent===false || $sent!==3){throw new RuntimeException('Message not sent completely');}
    lessonWaitRead($receiver);
    echo $sent, '/', stream_socket_recvfrom($receiver,64), "\n";
} finally {fclose($sender);fclose($receiver);}
Ожидаемый результат
3/tea

В этой лаборатории отдельно подтвердили получение собственным receiver. Одно значение 3 без read на получателе такого подтверждения не давало бы.

Вернуть ответ по адресу из recvfromСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/udp-lab.php';
[$sender,$receiver,$address]=lessonUdpPair();
try {
    stream_socket_sendto($sender,'PING',0,$address);
    lessonWaitRead($receiver);
    $request=stream_socket_recvfrom($receiver,64,0,$from);
    $reply=$request==='PING' ? 'PONG' : 'FAIL';
    if(stream_socket_sendto($receiver,$reply,0,$from)!==4){throw new RuntimeException('Reply failed');}
    lessonWaitRead($sender);
    echo stream_socket_recvfrom($sender,64), "\n";
} finally {fclose($sender);fclose($receiver);}
Ожидаемый результат
PONG

Адрес явно относится к нашему фиксированному локальному отправителю. Реальный публичный UDP-сервис должен учитывать подмену source address, повторные сообщения и недопустимость неограниченного усиления ответа.

Пустой address использует адрес, выбранный при создании соединённого socket stream. Альтернативный адрес задаётся числовым IPv4:port либо [IPv6]:port; не подставляйте произвольное имя хоста в надежде на DNS-разрешение этого параметра. flags поддерживает STREAM_OOB, если транспорт это умеет; STREAM_PEEK здесь не разрешение на «предварительную отправку». TCP тоже может использовать sendto, но протокольные границы сообщений и частичные передачи от этого не исчезают.

Связанные операции: stream_socket_recvfrom, stream_socket_client, stream_socket_get_name.

Маршрут от первого потока к контекстам и фильтрам.

stream_socket_enable_cryptoСогласовать TLS на уже открытом socket stream, различая успех, отказ и продолжение non-blocking handshake.

PHP 5.1.0+ · standard

stream_socket_enable_crypto(resource $stream, bool $enable, ?int $crypto_method = null, ?resource $session_stream = null): int|bool

Согласовать TLS на уже открытом socket stream, различая успех, отказ и продолжение non-blocking handshake.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamПодходящий соединённый socket stream; требуется поддержка криптографии, обычно OpenSSL.
enabletrue включить TLS, false выключить по договору протокола; это не команда зашифровать произвольную строку.
crypto_methodCLIENT/SERVER маска допустимых методов; null использует ssl.crypto_method context.
session_streamНеобязательный совместимый поток для параметров сессии, не гарантия её возобновления; nullable с PHP 8.0.
Возвратtrue успех; false отказ; 0 продолжить non-blocking согласование. Проверять строго.
Отказы / границыОшибки транспорта/сертификата могут выдать warning и false; неверные ресурсы/типы TypeError. Crypto без проверки peer identity не обеспечивает нужное доверие.
Защита до полезных данных
  1. TCP уже создан
  2. Обе стороны выполняют TLS handshake
  3. Клиент проверяет сертификат и имя
  4. Только true разрешает передавать защищённые данные

Обычный TCP передаёт байты без защиты транспортного канала. Протокол может требовать TLS сразу после connect или согласованный переход STARTTLS. enable_crypto запускает TLS на уже существующем socket stream. Проверка сертификата, имени сервера и согласованного протокола нужна до передачи секретов; просто наличие шифрования ещё не подтверждает личность другой стороны.

Здесь приложены tcp-lab.php, tls-lab.php и tls.cnf. Лаборатория создаёт одноразовый ключ/сертификат только для localhost, сохраняет их во временную папку примера и настраивает клиенту cafile и peer_name=localhost. verify_peer и verify_peer_name включены. Обе стороны соединяются только через 127.0.0.1. Helpers последовательно продвигают два non-blocking handshake с общим пределом ожидания, затем возвращают blocking для короткого сообщения. Это контролируемая лаборатория, не общий TLS event loop для любой нагрузки.

Отличить незавершённое TLS-рукопожатие от отказаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tls-lab.php';
[$client,$peer]=lessonTlsSockets();
try {
    $state=stream_socket_enable_crypto($client,true,
        STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT);
    if($state===false){throw new RuntimeException('TLS failed');}
    echo $state===0 ? "Нужно продолжить handshake\n" : "TLS уже установлен\n";
    $completed=lessonTlsHandshake($client,$peer);
    var_dump($completed['client'],$completed['server']);
} finally {fclose($client);fclose($peer);}
Ожидаемый результат
Нужно продолжить handshake
bool(true)
bool(true)

Серверная сторона ещё не выполняла свою часть, поэтому первый non-blocking вызов вернул 0. Проверка if (!$state) ошибочно назвала бы это отказом. Флаг | допускает современный TLS 1.2 или 1.3; клиентские и серверные роли различаются.

Передать сообщение после успешного TLS и посмотреть сведения каналаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/tls-lab.php';
[$client,$peer]=lessonTlsSockets();
try {
    lessonTlsHandshake($client,$peer);
    lessonWriteAll($client,'receipt');
    echo lessonReadExact($peer,7), "\n";
    $crypto=stream_get_meta_data($client)['crypto'];
    var_dump(in_array($crypto['protocol'],['TLSv1.2','TLSv1.3'],true));
    var_dump(isset($crypto['cipher_name'],$crypto['cipher_bits']));
} finally {fclose($client);fclose($peer);}
Ожидаемый результат
receipt
bool(true)
bool(true)

Проверен настоящий TLS с доверенным только для этого опыта одноразовым сертификатом и проверкой имени localhost. Конкретный cipher не закреплён как универсальный: он зависит от версии OpenSSL и согласования. После теста вся временная папка удаляется общим проверяющим инструментом.

Не пытаться включить TLS на обычном memory streamСамостоятельный пример
<?php
declare(strict_types=1);

$stream=fopen('php://memory','w+');
$warned=false;
set_error_handler(function($level) use (&$warned){
    if($level===E_WARNING){$warned=true;return true;}return false;
});
try {$result=stream_socket_enable_crypto($stream,true,STREAM_CRYPTO_METHOD_TLS_CLIENT);}
finally {restore_error_handler();fclose($stream);}
var_dump($result,$warned);
Ожидаемый результат
bool(false)
bool(true)

TLS — протокол подходящего соединения, не универсальная функция шифрования произвольного файла. Для хранения зашифрованных данных нужны другие криптографические API и управление ключами.

Группа crypto_methodЧто означает и как относиться
TLSv1_2_CLIENT / SERVER; TLSv1_3_CLIENT / SERVERКонкретная версия и роль; TLS 1.3 константы доступны с PHP 7.4. Можно объединять разрешённые методы по согласованной политике.
TLS_CLIENT / SERVERНабор TLS-версий, который определяет реализация. Не утверждайте, что имя само ограничивает его только 1.2/1.3.
ANY_CLIENT / SERVERШирокий набор SSL/TLS; наличие константы не рекомендация включать устаревшие протоколы.
SSLv2, SSLv3, SSLv23 и TLSv1_0, TLSv1_1 с CLIENT/SERVERИсторические методы остаются в официальном перечне. Поддержка может быть отключена OpenSSL; не выбирайте их ради успешного соединения с устаревшим сервером. SSLv23 — историческое имя набора, не надёжная современная политика версий.
crypto_method=nullИспользовать ssl.crypto_method контекста. Перед включением убедитесь, что такая настройка задана; отсутствие не является выбором вашей безопасной политики.

session_stream позволяет использовать параметры TLS-сессии из другого совместимого потока; он не переносит HTTP-аутентификацию, историю команд или гарантию resumption. Сервер/библиотека могут не возобновить сессию. Nullable session_stream поддерживается с PHP 8.0. Настройки сертификата относятся к SSL context, а не к аргументу session_stream как «любой уже залогиненный клиент».

enable=false отключает защиту, если это поддержано состоянием потока. Это не безопасный «следующий шаг после логина»: исторический пример мануала с plaintext после пароля не переносится в новый сервис. Менять режим можно лишь по явно согласованному протоколу. TLS не заменяет проверку полномочий, корректность API-команды или безопасность кода приложения. Для обычного HTTPS удобнее wrapper/HTTP-клиент, который организует жизненный цикл; низкоуровневый API нужен, когда вы действительно ведёте такой протокол.

Связанные операции: stream_socket_client, stream_context_create, stream_select, stream_get_meta_data.

Маршрут от первого потока к контекстам и фильтрам.

stream_filter_appendДобавить преобразование в конец цепочки чтения или записи открытого потока.

PHP 4.3.0+ · standard

stream_filter_append(resource $stream, string $filter_name, int $mode = 0, mixed $params = null): resource|false

Добавить преобразование в конец цепочки чтения или записи открытого потока.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый поток; отдельно выбирайте направление при режиме с +.
filter_nameЗарегистрированное имя; встроенное или своё после stream_filter_register.
mode0 выводит направления из режима открытия; READ/WRITE/ALL задают их явно.
paramsЛюбое значение по контракту обработчика, например массив параметров Base64.
ВозвратРесурс экземпляра для stream_filter_remove; false при отказе создания.
Порядок / буферыДобавление в конец; READ-буфер отличается от поведения второй операции, как описано выше.
Из строки в байты хранилища
  1. fwrite передаёт вход
  2. Первый фильтр меняет байты
  3. Следующие получают его результат
  4. Поток сохраняет конечные байты

Нужно преобразовать выгрузку при записи, не собирая второй полный файл в переменной. Сначала откройте поток, затем прикрепите обработчик к направлению WRITE. Он меняет байты между fwrite и хранилищем. READ действует между хранилищем и читающим кодом. Обработчик не делает весь файл «изменённым навсегда»: это отдельная стадия конкретного открытого потока.

Превратить короткую ASCII-строку в верхний регистр при записиСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $filter = stream_filter_append($stream, 'string.toupper', STREAM_FILTER_WRITE);
    if ($filter === false) { throw new RuntimeException('Filter unavailable'); }
    fwrite($stream, "tea\n");
    stream_filter_remove($filter);
    rewind($stream);
    echo stream_get_contents($stream);
} finally { fclose($stream); }
Ожидаемый результат
TEA

Это встроенный байтовый фильтр и ASCII-текст, не универсальное изменение регистра Unicode. После remove оставшиеся данные обработчика дописываются; rewind перемещает позицию для чтения результата.

Поставить нормализацию перед транспортным кодированиемСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
$stream = fopen('php://temp', 'w+');
try {
    stream_filter_append($stream, 'string.toupper', STREAM_FILTER_WRITE);
    $last = stream_filter_append($stream, 'convert.base64-encode', STREAM_FILTER_WRITE,
        ['line-length' => 0]);
    fwrite($stream, 'tea');
    stream_filter_remove($last);
    rewind($stream);
    echo stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
VEVB

Порядок вычисления: tea → TEA → VEVB. Последний фильтр кодирует уже преобразованные данные. Base64 не шифрует и не скрывает секреты.

Важное различие при чтении уже начатого потока: append сразу прогоняет через новый фильтр байты, оставшиеся во внутреннем буфере. prepend не переобрабатывает накопленные байты, уже прошедшие прежнюю цепочку. Поэтому цепь удобно собрать до первого чтения. Перемотка и изменение цепочки посередине требуют отдельной проверки формата и положения.

Для режима r+/w+ значение mode по умолчанию может создать одновременно READ и WRITE экземпляры. Это два состояния фильтра, а один возвращённый handle не даёт отдельно управлять обоими. Если нужны оба, вызовите функцию дважды с явными направлениями и сохраните оба ресурса. STREAM_FILTER_ALL = READ | WRITE; params передаются конкретному фильтру и имеют его собственный контракт.

Связанные операции: stream_filter_remove, stream_filter_register, stream_filter_prepend, stream_get_filters.

Маршрут от первого потока к контекстам и фильтрам.

stream_filter_prependДобавить преобразование в начало цепочки чтения или записи открытого потока.

PHP 4.3.0+ · standard

stream_filter_prepend(resource $stream, string $filter_name, int $mode = 0, mixed $params = null): resource|false

Добавить преобразование в начало цепочки чтения или записи открытого потока.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamОткрытый поток; отдельно выбирайте направление при режиме с +.
filter_nameЗарегистрированное имя; встроенное или своё после stream_filter_register.
mode0 выводит направления из режима открытия; READ/WRITE/ALL задают их явно.
paramsЛюбое значение по контракту обработчика, например массив параметров Base64.
ВозвратРесурс экземпляра для stream_filter_remove; false при отказе создания.
Порядок / буферыДобавление в начало; READ-буфер отличается от поведения второй операции, как описано выше.
Из строки в байты хранилища
  1. fwrite передаёт вход
  2. Первый фильтр меняет байты
  3. Следующие получают его результат
  4. Поток сохраняет конечные байты

Нужно преобразовать выгрузку при записи, не собирая второй полный файл в переменной. Сначала откройте поток, затем прикрепите обработчик к направлению WRITE. Он меняет байты между fwrite и хранилищем. READ действует между хранилищем и читающим кодом. Обработчик не делает весь файл «изменённым навсегда»: это отдельная стадия конкретного открытого потока.

Превратить короткую ASCII-строку в верхний регистр при записиСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $filter = stream_filter_prepend($stream, 'string.toupper', STREAM_FILTER_WRITE);
    if ($filter === false) { throw new RuntimeException('Filter unavailable'); }
    fwrite($stream, "tea\n");
    stream_filter_remove($filter);
    rewind($stream);
    echo stream_get_contents($stream);
} finally { fclose($stream); }
Ожидаемый результат
TEA

Это встроенный байтовый фильтр и ASCII-текст, не универсальное изменение регистра Unicode. После remove оставшиеся данные обработчика дописываются; rewind перемещает позицию для чтения результата.

Поставить нормализацию перед транспортным кодированиемСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $base64 = stream_filter_append($stream, 'convert.base64-encode', STREAM_FILTER_WRITE);
    stream_filter_prepend($stream, 'string.toupper', STREAM_FILTER_WRITE);
    fwrite($stream, 'tea');
    stream_filter_remove($base64);
    rewind($stream);
    echo stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
VEVB

Хотя Base64 добавили раньше, prepend вставил верхний регистр перед ним. Порядок стадий определяется цепочкой, а не только временем регистрации классов.

Важное различие при чтении уже начатого потока: append сразу прогоняет через новый фильтр байты, оставшиеся во внутреннем буфере. prepend не переобрабатывает накопленные байты, уже прошедшие прежнюю цепочку. Поэтому цепь удобно собрать до первого чтения. Перемотка и изменение цепочки посередине требуют отдельной проверки формата и положения.

Для режима r+/w+ значение mode по умолчанию может создать одновременно READ и WRITE экземпляры. Это два состояния фильтра, а один возвращённый handle не даёт отдельно управлять обоими. Если нужны оба, вызовите функцию дважды с явными направлениями и сохраните оба ресурса. STREAM_FILTER_ALL = READ | WRITE; params передаются конкретному фильтру и имеют его собственный контракт.

Связанные операции: stream_filter_remove, stream_filter_register, stream_filter_append, stream_get_filters.

Маршрут от первого потока к контекстам и фильтрам.

stream_filter_registerСвязать имя фильтра с классом, который PHP будет создавать для цепочки потока.

PHP 5+ · standard

stream_filter_register(string $filter_name, string $class): bool

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

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
filter_nameИмя или семейство наподобие lesson.*; уже занятое имя даёт false.
classИмя доступного класса-наследника php_user_filter; ::class удобно избегает ручной строки.
Возвратtrue запись реестра создана; false регистрация не удалась.
ИнициализацияonCreate при создании экземпляра; регистрация и выполнение filter — разные события.
Три разных шага
  1. Объявить класс обработки
  2. Зарегистрировать имя → класс
  3. Прикрепить экземпляр к потоку
  4. Читать/писать — PHP вызывает filter

Регистрация связывает имя с вашим классом; она ещё не открывает поток и не преобразует данные. Класс наследуется от php_user_filter. Для инициализации PHP вызывает onCreate, а не пользовательский __construct. Самостоятельный вызов new не заменяет прикрепление фильтра к потоку.

Зарегистрировать прозрачный фильтр и применить егоСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
var_dump(stream_filter_register('lesson.copy', LessonCopyFilter::class));
echo lessonFilter('lesson.copy', ['tea', '+', 'milk']), "\n";
Ожидаемый результат
bool(true)
tea+milk

Определение LessonCopyFilter находится в приложенном filter-lab.php: пока он просто переносит каждый входной bucket в выход. ::class возвращает имя класса; это не вызов метода. Следующий шаг — изменить данные внутри filter.

Несколько явно разрешённых имён через одну фабрикуСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class NamedCase extends php_user_filter {
    public function onCreate(): bool {
        return in_array($this->filtername, ['lessoncase.upper', 'lessoncase.lower'], true);
    }
    public function filter($in, $out, &$consumed, bool $closing): int {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += strlen($bucket->data);
            $bucket->data = $this->filtername === 'lessoncase.upper'
                ? strtoupper($bucket->data) : strtolower($bucket->data);
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('lessoncase.*', NamedCase::class);
echo lessonFilter('lessoncase.upper', ['Tea']), "\n";
echo lessonFilter('lessoncase.lower', ['Tea']), "\n";
var_dump(stream_filter_register('lessoncase.*', NamedCase::class));
Ожидаемый результат
TEA
tea
bool(false)

Звёздочка даёт общий зарегистрированный префикс, но класс сам проверяет конкретное filtername. Повторная регистрация того же имени возвращает false. Это не маршрутизация к любому методу класса и не разрешение имени из недоверенного запроса.

Нет парной функции stream_filter_unregister. Реестр принадлежит текущему выполнению PHP; в долгоживущем worker выбирайте стабильные имена, не регистрируйте новый случайный класс на каждое сообщение. Регистрация сама по себе не доказывает, что класс удастся создать: ошибки класса/наследования/методов могут проявиться при прикреплении. Сигнатуры должны соответствовать php_user_filter, consumed обязательно по ссылке.

Связанные операции: php_user_filter::onCreate, php_user_filter::filter, stream_filter_append, stream_get_filters.

Маршрут от первого потока к контекстам и фильтрам.

stream_filter_removeЗавершить накопленные данные и отсоединить конкретный фильтр, сохранив поток открытым.

PHP 5.1.0+ · standard

stream_filter_remove(resource $stream_filter): bool

Завершить накопленные данные и отсоединить конкретный фильтр, сохранив поток открытым.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
stream_filterЖивой ресурс фильтра, не ресурс самого файла.
Возвратtrue удалён; false не удалось завершить/удалить.
Побочные эффектыФинальные данные передаются следующему фильтру; onClose освобождает ресурсы обработчика.
ОшибкиНеверный/повторно удалённый ресурс TypeError в PHP 8; ошибки фильтра могут сопровождаться warning.
Финализация без отката
  1. Вход больше не поступает этому фильтру
  2. Накопленный хвост проходит дальше
  3. Экземпляр отключается
  4. Поток остаётся открытым

Handle фильтра — отдельный ресурс, полученный append/prepend. Удаление сначала просит фильтр завершить накопленные данные и передать их дальше по цепочке. Оно не отменяет уже записанные байты и не закрывает сам поток.

Преобразовать только начало сообщенияСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $filter = stream_filter_append($stream, 'string.toupper', STREAM_FILTER_WRITE);
    fwrite($stream, 'tea');
    var_dump(stream_filter_remove($filter));
    fwrite($stream, '+milk');
    rewind($stream);
    echo stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
bool(true)
TEA+milk

Удалён только обработчик. Открытый поток продолжает принимать обычные байты; уже превращённое TEA остаётся таким.

Завершить блок Base64 до чтения результатаСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $filter = stream_filter_append($stream, 'convert.base64-encode', STREAM_FILTER_WRITE);
    fwrite($stream, 'a');
    var_dump(stream_filter_remove($filter));
    rewind($stream);
    echo stream_get_contents($stream), "\n";
    var_dump(is_resource($filter));
} finally { fclose($stream); }
Ожидаемый результат
bool(true)
YQ==
bool(false)

Один байт ещё не образует полный блок Base64. Финализация дописывает завершающий блок с padding. После успешного удаления сохранённая переменная уже не содержит живой ресурс фильтра; повторный remove — ошибка, не идемпотентный cleanup.

Связанные операции: stream_filter_append, php_user_filter::onClose, php_user_filter::filter.

Маршрут от первого потока к контекстам и фильтрам.

stream_get_filtersПолучить зарегистрированные имена и семейства фильтров текущей сборки и выполнения PHP.

PHP 5+ · standard

stream_get_filters(): array

Получить зарегистрированные имена и семейства фильтров текущей сборки и выполнения PHP.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
ВозвратИндексный массив строк; перечень зависит от расширений и пользовательских регистраций.
ГраницаНе возвращает живые handles, параметры/порядок цепочки конкретного потока или гарантию успеха преобразования.
Проверка возможностей
  1. PHP загружает расширения
  2. Приложение регистрирует свои имена
  3. get_filters возвращает реестр
  4. Прикрепление проверяет конкретную конфигурацию

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

Проверить готовое преобразование артикулаСамостоятельный пример
<?php
declare(strict_types=1);

if (!in_array('string.toupper', stream_get_filters(), true)) {
    throw new RuntimeException('Нет преобразования ASCII-артикулов');
}
$stream = fopen('php://memory', 'w+b');
try {
    $filter = stream_filter_append($stream, 'string.toupper', STREAM_FILTER_WRITE);
    if ($filter === false) { throw new RuntimeException('Не подключить фильтр'); }
    fwrite($stream, 'tea-104');
    stream_filter_remove($filter);
    rewind($stream); echo stream_get_contents($stream), "\n";
} finally { fclose($stream); }
Ожидаемый результат
TEA-104

Реестр проверяется до работы, затем отдельно проверяется прикрепление. ASCII-артикул преобразуется в наблюдаемый результат. Семейства с * в реестре не перечисляют каждое допустимое конкретное имя фильтра.

Увидеть собственную регистрацию в списке возможностейСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
var_dump(in_array('lesson.export', stream_get_filters(), true));
stream_filter_register('lesson.export', LessonCopyFilter::class);
var_dump(in_array('lesson.export', stream_get_filters(), true));
echo lessonFilter('lesson.export', ['order=12']), "\n";
Ожидаемый результат
bool(false)
bool(true)
order=12

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

Связанные операции: stream_filter_register, stream_filter_append, stream_get_wrappers, stream_get_transports.

Маршрут от первого потока к контекстам и фильтрам.

stream_bucket_appendПоместить обработанный блок в конец выходной очереди фильтра.

PHP 5+; StreamBucket PHP 8.4+ · standard

stream_bucket_append(resource $brigade, StreamBucket $bucket): void

Поместить обработанный блок в конец выходной очереди фильтра.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
brigadeРесурс очереди из filter, обычно $out; не ресурс файла.
bucketБлок от make_writeable или new; StreamBucket с PHP 8.4, ранее stdClass.
Возвратvoid: полезный эффект — изменение очереди, не новая строка.
ОшибкиНеверный тип очереди/объекта/внутреннего ресурса даёт TypeError либо отказ API; это внутренний протокол, а не произвольный массив.
Передача владения блоком
  1. Входной bucket извлечён
  2. Исходные байты учтены
  3. Данные при необходимости изменены
  4. Bucket помещён в конец выхода

Внутри filter вход $in и выход $out — не строки, а ресурсы очередей блоков (bucket brigades). make_writeable извлекает очередной блок из входа; $bucket->data даёт его строку байтов. append/prepend помещает блок в выход. Таким образом, «извлечь» ещё не значит «передать»: забытый выходной блок означает потерянные данные. Размер блока не обещает совпадения со строкой CSV, символом UTF-8 или вызовом fwrite.

Первое действие — перенести блок без изменения. consumed увеличиваем на исходную длину до любой замены. Очередь хранит порядок; append ставит очередной результат после уже подготовленных выходных блоков.

Не потерять байты в прозрачном обработчикеСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class PassBytes extends php_user_filter {
    public function filter($in, $out, &$consumed, bool $closing): int {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += strlen($bucket->data);
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('lesson.pass', PassBytes::class);
echo lessonFilter('lesson.pass', ['tea', '+milk']), "\n";
Ожидаемый результат
tea+milk

while выполняет тело для каждого доступного блока. Когда входная очередь опустела, make_writeable возвращает null и цикл заканчивается. PASS_ON разрешает передать подготовленный выход дальше.

Увеличить выход, не завысив количество принятого входаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class EscapeComma extends php_user_filter {
    public function filter($in, $out, &$consumed, bool $closing): int {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += strlen($bucket->data);
            $bucket->data = str_replace(',', '[comma]', $bucket->data);
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('lesson.comma', EscapeComma::class);
echo lessonFilter('lesson.comma', ['tea,milk']), "\n";
Ожидаемый результат
tea[comma]milk

На входе 8 байтов, на выходе больше. consumed относится к 8 принятым байтам, а не к длине результата. Это демонстрация замены одного ASCII-байта, не корректный CSV-encoder: для CSV используйте fputcsv с нужным форматом.

Связанные операции: stream_bucket_make_writeable, stream_bucket_prepend, stream_bucket_new, php_user_filter::filter.

Маршрут от первого потока к контекстам и фильтрам.

stream_bucket_prependВставить блок в начало текущей очереди, не меняя уже отправленную часть потока.

PHP 5+; StreamBucket PHP 8.4+ · standard

stream_bucket_prepend(resource $brigade, StreamBucket $bucket): void

Вставить блок в начало текущей очереди, не меняя уже отправленную часть потока.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
brigadeОчередь blocks, обычно $out внутри filter.
bucketДействительный StreamBucket (8.4+) либо исторически stdClass от bucket API.
Возвратvoid.
ПорядокНовый блок становится первым в этой очереди; несколько prepend меняют относительный порядок.
ОшибкиНеверные ресурсы/тип объекта отвергаются; вставка во вход требует защиты от повторной бесконечной обработки.
Вставка перед очередью
  1. Выход содержит полезный блок
  2. Создан отдельный префикс
  3. prepend помещает префикс первым
  4. Ранее отправленные байты не трогаются

Внутри filter вход $in и выход $out — не строки, а ресурсы очередей блоков (bucket brigades). make_writeable извлекает очередной блок из входа; $bucket->data даёт его строку байтов. append/prepend помещает блок в выход. Таким образом, «извлечь» ещё не значит «передать»: забытый выходной блок означает потерянные данные. Размер блока не обещает совпадения со строкой CSV, символом UTF-8 или вызовом fwrite.

prepend нужен, когда новый блок должен оказаться перед тем, что уже добавлено в этой выходной очереди. Он не перемещает назад байты, уже отправленные предыдущими вызовами filter. Поэтому простой prepend каждого блока не разворачивает весь файл задом наперёд.

Вставить один префикс перед первым полезным блокомСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class PrefixOnce extends php_user_filter {
    private bool $started = false;
    public function filter($in, $out, &$consumed, bool $closing): int {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += strlen($bucket->data);
            stream_bucket_append($out, $bucket);
            if (!$this->started) {
                stream_bucket_prepend($out, stream_bucket_new($this->stream, 'items:'));
                $this->started = true;
            }
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('lesson.prefix', PrefixOnce::class);
echo lessonFilter('lesson.prefix', ['tea', ',milk']), "\n";
Ожидаемый результат
items:tea,milk

Свойство started принадлежит экземпляру фильтра и переживает несколько его вызовов. Заголовок добавлен один раз перед первым входом. Для пустого входа это правило не печатает заголовок: такой случай нужно выбирать осознанно.

Увидеть порядок двух созданных выходных блоковСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class MarkChunk extends php_user_filter {
    public function filter($in, $out, &$consumed, bool $closing): int {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += strlen($bucket->data);
            stream_bucket_append($out, stream_bucket_new($this->stream, $bucket->data . ']'));
            stream_bucket_prepend($out, stream_bucket_new($this->stream, '['));
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('lesson.chunk', MarkChunk::class);
echo lessonFilter('lesson.chunk', ['tea']), "\n";
Ожидаемый результат
[tea]

Это опыт с одним небольшим входным bucket: скобка встаёт перед уже добавленным результатом. Не превращайте его в форматирование записей реального файла: при нескольких buckets/calls границы будут другими. Для строк нужен накопитель до разделителя, как в примере filter.

Допустимо возвращать bucket в начало входной очереди для специального алгоритма, но без изменения условия цикл тут же извлечёт тот же блок снова. Это легко создаёт бесконечную обработку. Начинайте с одностороннего движения $in → $out; не увеличивайте consumed второй раз для одних и тех же принятых байтов.

Связанные операции: stream_bucket_append, stream_bucket_new, php_user_filter::filter.

Маршрут от первого потока к контекстам и фильтрам.

stream_bucket_make_writeableИзвлечь следующий входной блок для изменения либо получить null, когда очередь пуста.

PHP 5+; StreamBucket PHP 8.4+ · standard

stream_bucket_make_writeable(resource $brigade): ?StreamBucket

Извлечь следующий входной блок для изменения либо получить null, когда очередь пуста.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
brigadeРесурс очереди, переданный PHP в filter, а не строка/файл.
ВозвратStreamBucket или null; до PHP 8.4 возвращался stdClass.
Побочный эффектБлок извлекается из очереди; его нужно передать дальше, преобразовать или сохранить данные по своему алгоритму.
ОшибкиНеверный вид ресурса/тип — ошибка; пустая очередь является нормальным null.
Один шаг цикла
  1. Входная очередь содержит блоки
  2. make_writeable извлекает первый
  3. Код обрабатывает его data
  4. Следующий вызов берёт следующий либо null

Внутри filter вход $in и выход $out — не строки, а ресурсы очередей блоков (bucket brigades). make_writeable извлекает очередной блок из входа; $bucket->data даёт его строку байтов. append/prepend помещает блок в выход. Таким образом, «извлечь» ещё не значит «передать»: забытый выходной блок означает потерянные данные. Размер блока не обещает совпадения со строкой CSV, символом UTF-8 или вызовом fwrite.

Имя функции написано writeable именно так. Она забирает следующий доступный bucket из brigade и даёт обёртку для изменения. null означает, что сейчас брать нечего; это не отдельный пустой bucket. Цикл должен передать или осознанно задержать каждый извлечённый вход.

Изменить очередной доступный блокСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class UpperBytes extends php_user_filter {
    public function filter($in, $out, &$consumed, bool $closing): int {
        while (($bucket = stream_bucket_make_writeable($in)) !== null) {
            $consumed += strlen($bucket->data);
            $bucket->data = strtoupper($bucket->data);
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('lesson.upper', UpperBytes::class);
echo lessonFilter('lesson.upper', ['tea']), "\n";
Ожидаемый результат
TEA

===/!== проверяют результат строго; null завершает цикл. В этом ASCII-примере преобразование не зависит от границы блока. Реальная многобайтная нормализация требует согласованной кодировки и учёта разрезанных символов.

Учесть удалённые из результата входные байтыСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class RemoveZeroByte extends php_user_filter {
    public function filter($in, $out, &$consumed, bool $closing): int {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += strlen($bucket->data);
            $bucket->data = str_replace("\0", '', $bucket->data);
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('lesson.nozero', RemoveZeroByte::class);
echo lessonFilter('lesson.nozero', ["te\0a"]), "\n";
Ожидаемый результат
tea

Фильтр принял четыре байта, хотя выпустил три. Удаление NUL здесь — явное правило игрушечного формата, а не безопасное исправление любого повреждённого файла; в протоколе может требоваться отказ вместо изменения данных.

Связанные операции: stream_bucket_append, stream_bucket_new, php_user_filter::filter.

Маршрут от первого потока к контекстам и фильтрам.

stream_bucket_newСоздать отдельный блок байтов для последующей передачи из пользовательского фильтра.

PHP 5+; StreamBucket PHP 8.4+ · standard

stream_bucket_new(resource $stream, string $buffer): StreamBucket

Создать отдельный блок байтов для последующей передачи из пользовательского фильтра.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
streamЖивой stream resource, обычно $this->stream во время filter; не brigade.
bufferСтрока новых байтов, включая допустимую пустую строку; функция не интерпретирует кодировку.
ВозвратStreamBucket с PHP 8.4, до этого stdClass; создание не равно записи.
ОшибкиНеверный/закрытый ресурс или неверный тип аргумента — TypeError; память под данные всё равно требуется.
ОбъёмФильтр с накоплением всего входа не становится экономным только из-за слова stream. Ограничивайте собственный буфер.
Из накопителя в выход
  1. Пришли части логической строки
  2. Фильтр хранит ограниченный хвост
  3. new создаёт готовый блок
  4. append отправляет его дальше

Новый блок нужен, когда результат не совпадает с одним входным bucket: например, фильтр накопил части строки или должен выпустить завершающий маркер. stream — ресурс исходного потока, buffer — новые байты. Само создание ничего не записывает; bucket ещё надо поставить в $out.

Создать блок и увидеть, что поток пока пустСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+');
try {
    $bucket = stream_bucket_new($stream, 'tea');
    echo get_class($bucket), ':', $bucket->data, ':', $bucket->dataLength, "\n";
    echo fstat($stream)['size'], "\n";
} finally { fclose($stream); }
Ожидаемый результат
StreamBucket:tea:3
0

Пример использует StreamBucket и dataLength из PHP 8.4+. Создана обёртка блока из трёх байтов, но файл не изменён: вне callback у нас нет выходной brigade, куда положить этот блок.

Собрать строку, пришедшую несколькими частямиСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
stream_filter_register('lesson.lines', LessonLineFilter::class);
echo lessonFilter('lesson.lines', ['te', "a\nmi", 'lk']), "\n";
Ожидаемый результат
TEA
MILK

Полный класс приложен в filter-lab.php. Он принимает каждый байт, копит не более 1024 байт незавершённой строки, создаёт новый bucket при переводе строки и выпускает хвост при closing. Граница fwrite не становится границей записи.

Связанные операции: stream_bucket_append, stream_bucket_prepend, php_user_filter::filter.

Маршрут от первого потока к контекстам и фильтрам.

php_user_filter::filterПринять входные блоки, учесть байты и выдать преобразованные блоки либо запросить следующий вход.

PHP 5+ · standard

php_user_filter::filter(resource $in, resource $out, int &$consumed, bool $closing): int

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

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
in / outРесурсы входной и выходной brigade, предоставляемые PHP.
&consumedПрибавлять исходные принятые байты; ссылка обязательна, длина выхода может отличаться.
closingtrue завершающий проход; false обычная обработка. Это не размер пакета и не номер вызова.
ВозвратОдин из PSFS_PASS_ON, PSFS_FEED_ME, PSFS_ERR_FATAL.
Ошибка алгоритмаЗабытый bucket теряет данные, неверный consumed ломает учёт, неограниченный pending расходует память.
Механика одного вызова
  1. PHP передаёт входную очередь
  2. Фильтр принимает и учитывает байты
  3. Готовые блоки переходят в выход
  4. Статус сообщает: готово / жду / отказ

Внешний код вызывает fwrite или fread, а PHP внутри вызывает ваш filter. Параметры $in/$out — очереди блоков. &$consumed передан по ссылке: прибавляя к нему, вы сообщаете движку, сколько входных байтов приняли. $closing говорит, что это завершающий проход. Не считайте число вызовов: PHP выбирает разбиение данных самостоятельно.

Первое преобразование без накопителяСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class SpacesToDots extends php_user_filter {
    public function filter($in, $out, &$consumed, bool $closing): int {
        while ($bucket = stream_bucket_make_writeable($in)) {
            $consumed += strlen($bucket->data);
            $bucket->data = str_replace(' ', '.', $bucket->data);
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
stream_filter_register('lesson.dots', SpacesToDots::class);
echo lessonFilter('lesson.dots', ['tea ', 'and milk']), "\n";
Ожидаемый результат
tea.and.milk

Замена каждого ASCII-пробела не требует видеть следующий блок. Это подходящий первый filter: вход принят, преобразован и сразу отправлен. return сообщает статус обработки, а не возвращает текст.

Одинаковая логическая строка при разном разбиении входаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
stream_filter_register('lesson.lines', LessonLineFilter::class);
$whole = lessonFilter('lesson.lines', ["tea\nmilk"]);
$split = lessonFilter('lesson.lines', ['t', 'e', "a\nm", 'ilk']);
echo $whole, "\n";
var_dump($whole === $split);
Ожидаемый результат
TEA
MILK
bool(true)

Класс в приложенном файле создаёт bucket для готовой строки. Незавершённый хвост удерживается и при отсутствии готового выхода возвращается FEED_ME. closing выпускает последнюю строку без перевода. Данные уже приняты в собственный буфер, поэтому consumed увеличен сразу, не только в момент вывода.

Отклонить строку, превышающую договорённый лимитСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
stream_filter_register('lesson.lines', LessonLineFilter::class);
$stream = fopen('php://temp', 'w+');
$warning = false;
set_error_handler(function ($level) use (&$warning) {
    if ($level === E_WARNING) { $warning = true; return true; }
    return false;
});
try {
    stream_filter_append($stream, 'lesson.lines', STREAM_FILTER_WRITE);
    $written = fwrite($stream, str_repeat('x', 1025));
    var_dump($written === false);
} finally { fclose($stream); restore_error_handler(); }
Ожидаемый результат
bool(true)

PSFS_ERR_FATAL прекращает обработку. Не выдаём частичный результат за корректную выгрузку. 1024 — лимит только этого учебного строкового формата; посимвольный foreach прост для понимания, но для больших ASCII-потоков можно искать разделитель блоками, сохраняя тот же контракт.

СтатусЧто сообщает PHP
PSFS_PASS_ONОбработка успешна, выход готов для следующего звена.
PSFS_FEED_MEВход обработан, готового выхода пока нет; нужны дополнительные данные. Это не просьба повторно передать те же consumed байты.
PSFS_ERR_FATALНевосстановимый отказ фильтра; операция потока может завершиться ошибкой. Не подмена исключения успехом.

Во время closing вход может быть пуст, а ваш собственный буфер — нет. filter должен корректно завершить формат или отказать. onClose предназначен для освобождения ресурсов; выдавать хвост данных нужно здесь, где есть $out. Не логируйте сырые buckets, если они содержат токены/личные данные. Маскирование по одному bucket небезопасно: секрет может быть разрезан между ними.

Связанные операции: stream_bucket_make_writeable, stream_bucket_append, stream_bucket_new, php_user_filter::onCreate, php_user_filter::onClose.

Маршрут от первого потока к контекстам и фильтрам.

php_user_filter::onCreateПроверить параметры и подготовить состояние экземпляра до обработки данных.

PHP 5+ · standard

php_user_filter::onCreate(): bool

Проверить параметры и подготовить состояние экземпляра до обработки данных.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет; используются filtername и params, установленные PHP.
streamДоступность в onCreate не обещана; не используйте как готовый поток без проверки.
Возвратtrue готов; false создание фильтра не удалось.
Жизненный циклPHP вызывает вместо __construct при прикреплении фильтра; не при одной регистрации имени.
Подготовка экземпляра
  1. append выбирает зарегистрированный класс
  2. PHP задаёт имя и params
  3. onCreate проверяет и инициализирует
  4. true допускает первый filter

Один зарегистрированный класс может создавать отдельные экземпляры с разными настройками. onCreate проверяет params и готовит свойства до первого блока. __construct при создании фильтра движком не вызывается. Не открывайте сеть ради простой проверки параметра; отказ надо вернуть до обработки пользовательских данных.

Инициализировать настройку и увидеть момент созданияСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class PreparedCopy extends LessonCopyFilter {
    public static array $events = [];
    public function __construct() { self::$events[] = 'constructor'; }
    public function onCreate(): bool {
        self::$events[] = 'create:' . $this->filtername;
        return true;
    }
}
stream_filter_register('lesson.prepared', PreparedCopy::class);
echo lessonFilter('lesson.prepared', ['tea']), "\n";
echo implode(',', PreparedCopy::$events), "\n";
Ожидаемый результат
tea
create:lesson.prepared

PHP создал экземпляр для потока и вызвал onCreate. Записи constructor нет. Это правило именно жизненного цикла php_user_filter, не отмена обычного конструктора у всех объектов.

Отказать при неподдерживаемой конфигурацииСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class CheckedCopy extends LessonCopyFilter {
    public function onCreate(): bool {
        return is_array($this->params) && ($this->params['format'] ?? null) === 'plain';
    }
}
stream_filter_register('lesson.checked', CheckedCopy::class);
echo lessonFilter('lesson.checked', ['tea'], ['format' => 'plain']), "\n";
$stream = fopen('php://temp', 'w+');
set_error_handler(fn($level) => $level === E_WARNING);
try { var_dump(stream_filter_append($stream, 'lesson.checked', STREAM_FILTER_WRITE, ['format' => 'unknown'])); }
finally { restore_error_handler(); fclose($stream); }
Ожидаемый результат
tea
bool(false)

Неизвестный формат не превращается в «почти подходящий». Ошибка возникает в append ещё до записи. Warning перехвачен только внутри контролируемого опыта; в приложении сохраните безопасную диагностику причины.

filtername уже содержит конкретное имя, params — переданную настройку. На stream нельзя полагаться при onCreate: он может быть доступен только во время filter с closing=false. Не делайте здесь fstat($this->stream) как универсальный контракт. Храните отдельные ресурсы для дальнейшей обработки в объявленных свойствах; очистите их в onClose и при частично неудачной инициализации.

Связанные операции: stream_filter_register, stream_filter_append, php_user_filter::filter, php_user_filter::onClose.

Маршрут от первого потока к контекстам и фильтрам.

php_user_filter::onCloseОсвободить ресурсы экземпляра после завершения фильтра, не формируя новые выходные данные.

PHP 5+ · standard

php_user_filter::onClose(): void

Освободить ресурсы экземпляра после завершения фильтра, не формируя новые выходные данные.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
Возвратvoid; результат не используется как выход потока.
МоментПри завершении фильтра, в частности remove или закрытие потока, после финальной обработки.
ГраницаНе транзакция, не надёжная очередь заданий, не место для допечатывания хвоста через echo.
Сначала данные, потом уборка
  1. filter получает финальный проход
  2. Остаток данных передаётся в out
  3. onClose освобождает собственные ресурсы
  4. Экземпляр завершён

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

Увидеть освобождение после явного удаленияСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class ClosingCopy extends LessonCopyFilter {
    public static int $closed = 0;
    public function onClose(): void { self::$closed++; }
}
stream_filter_register('lesson.closing', ClosingCopy::class);
echo lessonFilter('lesson.closing', ['tea']), "\n";
echo ClosingCopy::$closed, "\n";
Ожидаемый результат
tea
1

Harness удалил фильтр перед чтением и затем закрыл поток. onClose сработал один раз для этого экземпляра. Статический счётчик здесь только наблюдатель теста, не подход к учёту оплат.

Завершить фильтр и сохранить итог его работыСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/filter-lab.php';
class AuditedCopy extends LessonCopyFilter {
    private $audit = null;
    private int $bytes = 0;
    public function onCreate(): bool {
        $this->audit = fopen(__DIR__ . '/filter-audit.txt', 'wb');
        return $this->audit !== false;
    }
    public function filter($in, $out, &$consumed, bool $closing): int {
        $before = $consumed;
        $status = parent::filter($in, $out, $consumed, $closing);
        $this->bytes += $consumed - $before;
        return $status;
    }
    public function onClose(): void {
        try {
            $line = 'Принято байтов: ' . $this->bytes . "\n";
            if (fwrite($this->audit, $line) !== strlen($line)) {
                throw new RuntimeException('Журнал фильтра записан не полностью');
            }
        } finally { fclose($this->audit); }
    }
}
stream_filter_register('lesson.audit', AuditedCopy::class);
echo lessonFilter('lesson.audit', ['104:', "ready\n"]);
echo file_get_contents(__DIR__ . '/filter-audit.txt');
Ожидаемый результат
104:ready
Принято байтов: 10

onCreate открывает только собственный файл; filter учитывает принятые байты; onClose закрывает журнал после записи безопасного счётчика, без текста заказа. parent::filter вызывает готовое копирование из родительского класса. Это наблюдаемая диагностика, не обязательная фиксация бизнес-операции: аварийное завершение не гарантирует onClose. Выходные данные фильтра завершают в filter с closing, не в onClose.

Параметров нет, возвращаемое значение игнорируется; современная сигнатура void. Последний вывод создавайте в filter($closing=true), где PHP ещё дал выходную очередь. Не вызывайте вручную onClose, пока фильтр живёт в цепочке: повторная уборка может испортить состояние. Ссылки на ресурсы проверяйте, а исключения из cleanup не должны скрывать первоначальную ошибку приложения.

Связанные операции: php_user_filter::filter, php_user_filter::onCreate, stream_filter_remove.

Маршрут от первого потока к контекстам и фильтрам.

stream_wrapper_registerСвязать собственную схему URL с классом-обработчиком потоков.

PHP 4.3.2+ · standard

stream_wrapper_register(string $protocol, string $class, int $flags = 0): bool

Связать собственную схему URL с классом-обработчиком потоков.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
protocolИмя схемы без ://, допустимые символы A–Z/a–z/0–9/./+/-; уже занятое имя нельзя перезаписать этим вызовом.
classИмя класса, реализующего callbacks streamWrapper-протокола; это не наследование от встроенного streamWrapper.
flags0 локальная схема; STREAM_IS_URL схема с URL-ограничениями PHP.
Возвратtrue зарегистрировано; false не удалось, часто с warning; ошибки типов/класса могут быть исключениями.
СрокРеестр текущего выполнения; открытие создаёт отдельные объекты wrapper.
Путь конкретного документа
  1. lesson связано с LessonFileBridge
  2. file_get_contents получает lesson://store/pickup.txt
  3. PHP создаёт объект и вызывает callbacks
  4. Читатель получает «Заказ104: выдать две дрели»

Нужно дать существующему коду привычный fopen, хотя данные находятся за вашей абстракцией. Регистрация связывает схему lesson с классом, и fopen("lesson://store/tea.txt", "rb") передаёт открытие его stream_open. Это не регистрация фильтра: wrapper предоставляет сам источник, а фильтр преобразует байты источника.

Дать библиотеке адрес учебного листа выдачиСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
if (!stream_wrapper_register('lesson', LessonFileBridge::class)) { throw new RuntimeException('Не зарегистрировать схему'); }
echo file_get_contents('lesson://store/pickup.txt');
Ожидаемый результат
Заказ104: выдать две дрели

lesson — выбранное имя схемы без ://; LessonFileBridge::class — строка с именем приложенного класса. Регистрация сама ничего не читает: file_get_contents обращается по адресу и PHP вызывает методы класса. Alias register_wrapper имеет тот же договор.

Не подменять занятое имя незаметно для других частей приложенияСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
stream_wrapper_register('lesson', LessonFileBridge::class);
$warning = false;
set_error_handler(function ($level) use (&$warning) {
    if ($level === E_WARNING) { $warning = true; return true; }
    return false;
});
try { var_dump(stream_wrapper_register('lesson', LessonFileBridge::class), $warning); }
finally { restore_error_handler(); stream_wrapper_unregister('lesson'); }
Ожидаемый результат
bool(false)
bool(true)

Уже зарегистрированная схема даёт отказ. В библиотеке лучше уникальное имя и явная ошибка настройки, чем unregister чужого обработчика. Реестр общий для текущего выполнения, а не для одного объекта.

protocol содержит только латинские буквы/цифры, точку, плюс и дефис; URL для этой системы должен использовать ://. flags=0 обозначает локальную схему, STREAM_IS_URL — URL-протокол с соответствующими ограничениями allow_url_fopen/allow_url_include. Флаг не проверяет адреса за вас: класс обязан ограничивать пути, полномочия и допустимые операции. Локальная маркировка не делает источник безопасным.

Имя класса должно разрешаться при регистрации; отсутствие класса может вызвать TypeError в современной PHP, некорректное имя/занятое имя — warning и false. У класса публично объявляют $context, чтобы PHP мог передать ресурс контекста без динамического свойства. Методы реализуют именно поддерживаемые операции: наличие stream_open не обещает работоспособности mkdir, stat и directory iteration.

Связанные операции: stream_wrapper_unregister, stream_wrapper_restore, stream_get_wrappers, streamWrapper::stream_open.

Маршрут от первого потока к контекстам и фильтрам.

stream_register_wrapperСвязать собственную схему URL с классом-обработчиком потоков.

PHP 4.3.0+; alias · standard

stream_register_wrapper(string $protocol, string $class, int $flags = 0): bool

Связать собственную схему URL с классом-обработчиком потоков.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
protocolИмя схемы без ://, допустимые символы A–Z/a–z/0–9/./+/-; уже занятое имя нельзя перезаписать этим вызовом.
classИмя класса, реализующего callbacks streamWrapper-протокола; это не наследование от встроенного streamWrapper.
flags0 локальная схема; STREAM_IS_URL схема с URL-ограничениями PHP.
Возвратtrue зарегистрировано; false не удалось, часто с warning; ошибки типов/класса могут быть исключениями.
СрокРеестр текущего выполнения; открытие создаёт отдельные объекты wrapper.
Путь конкретного документа
  1. Alias регистрирует LessonFileBridge под lesson
  2. URL выбирает зарегистрированный класс
  3. Методы открывают собственный pickup.txt
  4. Читатель видит лист выдачи

Нужно дать существующему коду привычный fopen, хотя данные находятся за вашей абстракцией. Регистрация связывает схему lesson с классом, и fopen("lesson://store/tea.txt", "rb") передаёт открытие его stream_open. Это не регистрация фильтра: wrapper предоставляет сам источник, а фильтр преобразует байты источника.

Дать библиотеке адрес учебного листа выдачиСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
if (!stream_register_wrapper('lesson', LessonFileBridge::class)) { throw new RuntimeException('Не зарегистрировать схему'); }
echo file_get_contents('lesson://store/pickup.txt');
Ожидаемый результат
Заказ104: выдать две дрели

lesson — выбранное имя схемы без ://; LessonFileBridge::class — строка с именем приложенного класса. Регистрация сама ничего не читает: file_get_contents обращается по адресу и PHP вызывает методы класса. Alias register_wrapper имеет тот же договор.

Не подменять занятое имя незаметно для других частей приложенияСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
stream_register_wrapper('lesson', LessonFileBridge::class);
$warning = false;
set_error_handler(function ($level) use (&$warning) {
    if ($level === E_WARNING) { $warning = true; return true; }
    return false;
});
try { var_dump(stream_register_wrapper('lesson', LessonFileBridge::class), $warning); }
finally { restore_error_handler(); stream_wrapper_unregister('lesson'); }
Ожидаемый результат
bool(false)
bool(true)

Уже зарегистрированная схема даёт отказ. В библиотеке лучше уникальное имя и явная ошибка настройки, чем unregister чужого обработчика. Реестр общий для текущего выполнения, а не для одного объекта.

protocol содержит только латинские буквы/цифры, точку, плюс и дефис; URL для этой системы должен использовать ://. flags=0 обозначает локальную схему, STREAM_IS_URL — URL-протокол с соответствующими ограничениями allow_url_fopen/allow_url_include. Флаг не проверяет адреса за вас: класс обязан ограничивать пути, полномочия и допустимые операции. Локальная маркировка не делает источник безопасным.

Имя класса должно разрешаться при регистрации; отсутствие класса может вызвать TypeError в современной PHP, некорректное имя/занятое имя — warning и false. У класса публично объявляют $context, чтобы PHP мог передать ресурс контекста без динамического свойства. Методы реализуют именно поддерживаемые операции: наличие stream_open не обещает работоспособности mkdir, stat и directory iteration.

Связанные операции: stream_wrapper_unregister, stream_wrapper_restore, stream_get_wrappers, streamWrapper::stream_open.

Маршрут от первого потока к контекстам и фильтрам.

stream_wrapper_unregisterУдалить схему из реестра будущих открытий, сохранив жизнь уже открытых потоков.

PHP 5.1.0+ · standard

stream_wrapper_unregister(string $protocol): bool

Удалить схему из реестра будущих открытий, сохранив жизнь уже открытых потоков.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
protocolИмя зарегистрированной схемы без ://.
Возвратtrue удалена; false отсутствует/операция не удалась, возможен warning.
ВосстановлениеВстроенную схему возвращает restore; свою нужно повторно register с классом.
Реестр и открытый ресурс различаются
  1. Схема зарегистрирована
  2. Открытый поток хранит свой wrapper
  3. unregister убирает запись схемы
  4. Живой ресурс работает до fclose

Отмена регистрации убирает имя из реестра будущих открытий. Уже открытый поток продолжает владеть своим экземпляром и ресурсом. Изменение общего реестра — существенный эффект: используйте для собственной схемы в лаборатории или строго контролируемого жизненного цикла приложения, не как незаметный побочный эффект библиотечной функции.

Убрать свою схему после опытаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php'; lessonRegister();
var_dump(in_array('lesson', stream_get_wrappers(), true));
var_dump(stream_wrapper_unregister('lesson'));
var_dump(in_array('lesson', stream_get_wrappers(), true));
Ожидаемый результат
bool(true)
bool(true)
bool(false)

Реестр изменился. Повторное открытие lesson:// теперь нельзя считать поддержанным; не делайте случайный fopen как проверку наличия схемы.

Различить существующий поток и будущие открытияСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php'; lessonRegister();
$stream = fopen('lesson://store/tea.txt', 'rb');
stream_wrapper_unregister('lesson');
try { echo stream_get_contents($stream); }
finally { fclose($stream); }
Ожидаемый результат
tea
milk

Открытый ресурс не исчез после unregister: он сохраняет экземпляр обработчика. Это не средство отозвать доступ ко всем уже открытым файлам.

Связанные операции: stream_wrapper_register, stream_wrapper_restore, stream_get_wrappers.

Маршрут от первого потока к контекстам и фильтрам.

stream_wrapper_restoreВернуть встроенный обработчик схемы PHP после его отмены или подмены.

PHP 5.1.0+ · standard

stream_wrapper_restore(string $protocol): bool

Вернуть встроенный обработчик схемы PHP после его отмены или подмены.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
protocolИмя встроенной схемы без ://.
Возвратtrue восстановлена; false встроенной записи нет/операция не удалась, возможен warning.
ГраницаНе восстанавливает произвольный пользовательский класс и не откатывает действия уже открытых потоков.
Восстановление встроенного обработчика
  1. PHP хранит исходный реестр
  2. unregister меняет активный реестр
  3. restore берёт встроенную реализацию
  4. Новые открытия используют её

restore возвращает исходный встроенный обработчик PHP, ранее отменённый или заменённый. Это не стек ваших регистраций: удалённый пользовательский класс PHP для такого восстановления не запоминает. Состояние меняется для всех последующих открытий текущего выполнения.

Восстановить встроенную data-схему в изолированном процессеСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(stream_wrapper_unregister('data'));
try { var_dump(in_array('data', stream_get_wrappers(), true)); }
finally { var_dump(stream_wrapper_restore('data')); }
echo file_get_contents('data://text/plain,tea'), "\n";
Ожидаемый результат
bool(true)
bool(false)
bool(true)
tea

Опыт выполняется отдельным CLI-процессом с доступной data-схемой. finally возвращает встроенный обработчик; это не пример изменения глобального реестра внутри работающего веб-приложения.

Увидеть, что свою схему требуется зарегистрировать зановоСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php'; lessonRegister();
stream_wrapper_unregister('lesson');
set_error_handler(fn($level) => $level === E_WARNING);
try { var_dump(stream_wrapper_restore('lesson')); }
finally { restore_error_handler(); }
var_dump(stream_wrapper_register('lesson', LessonFileBridge::class));
stream_wrapper_unregister('lesson');
Ожидаемый результат
bool(false)
bool(true)

У PHP нет встроенного протокола lesson. restore не знает прежний пользовательский класс; повторная register явно передаёт нужную реализацию.

Связанные операции: stream_wrapper_unregister, stream_wrapper_register, stream_get_wrappers.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_openПодготовить экземпляр wrapper для конкретного адреса и режима открытия.

PHP 4.3.2+ · standard

streamWrapper::stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool

Подготовить экземпляр wrapper для конкретного адреса и режима открытия.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
pathПолный URL с ://; проверять допустимые ресурсы.
modeРежим fopen, который реализация обязана корректно поддержать либо отвергнуть.
optionsБитовая маска STREAM_USE_PATH и STREAM_REPORT_ERRORS.
&opened_pathПо ссылке вернуть действительный полный путь при поиске через include_path.
Возврат / ошибкиtrue готов, false отказ; отсутствующий callback вызывает E_WARNING. Собственные сообщения учитывать по REPORT_ERRORS.
Создание открытого потока
  1. Код передаёт URL и режим в fopen
  2. PHP создаёт объект wrapper
  3. stream_open проверяет и открывает источник
  4. true даёт вызывающему коду stream resource

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

Обычный вызывающий код знает адрес и режим, но не знает устройство источника. stream_open должен проверить их, открыть внутренний ресурс и сохранить его в свойстве объекта. Возвращаемое true означает «экземпляр готов»; строку содержимого этот callback не возвращает.

Открыть адрес и прочитать содержимоеСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try { echo stream_get_contents($stream); }
        finally { fclose($stream); }
        var_dump(in_array('open', LessonFileBridge::$events, true));
Ожидаемый результат
tea
milk
bool(true)

fopen передал URL и rb нашему объекту. Wrapper открыл внутренний файл, а последующее чтение пошло через stream_read. rb означает двоичное чтение; это не формат CSV/JSON.

Отказать адресам за пределами учебного хранилищаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        set_error_handler(fn($level) => in_array($level, [E_WARNING, E_USER_WARNING], true));
        try { $stream = fopen('lesson://other/pickup.txt', 'rb'); }
        finally { restore_error_handler(); }
        if ($stream === false) { echo "Этот адрес не принадлежит учебному хранилищу.\n"; }
        else { fclose($stream); }
        echo file_get_contents('lesson://store/pickup.txt');
Ожидаемый результат
Этот адрес не принадлежит учебному хранилищу.
Заказ104: ready

stream_open проверяет path и mode до открытия. Отказ чужому адресу не мешает читать разрешённый. Обработчик предупреждения ограничен одним опытом; полный helper остаётся учебным адаптером, не защитой от всех гонок/ссылок.

path приходит в форме scheme://…; варианты scheme: и scheme:/ этот протокол не поддерживает. parse_url может помочь разделить части, но не валидирует полномочия/безопасность. mode следует контракту fopen: r требует существующий файл, w обнуляет, a дописывает, x требует нового файла, c создаёт без обнуления, + разрешает чтение/запись; wrapper обязан выбрать поддерживаемые режимы, а не молча заменить их.

options — битовая маска: STREAM_USE_PATH просит учитывать include_path; при успешном поиске &$opened_path должен получить действительный полный адрес открытого ресурса. STREAM_REPORT_ERRORS определяет, должен ли wrapper сообщать ошибки открытия. В этой лаборатории разрешены абсолютные lesson://store/... адреса без поиска include_path; она не изображает общий resolver. $context PHP задаёт публичному свойству при переданном контексте.

Связанные операции: stream_wrapper_register, streamWrapper::__construct, streamWrapper::stream_read, stream_context_get_options.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_readВернуть следующую порцию байтов из источника и обновить его позицию.

PHP 4.3.2+ · standard

streamWrapper::stream_read(int $count): string|false

Вернуть следующую порцию байтов из источника и обновить его позицию.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
countМаксимум байтов, запрошенный движком, не обещанный размер записи приложения.
ВозвратСтрока длиной ≤count; пустая строка когда данных нет, false при отказе.
СостояниеПродвинуть внутреннюю позицию на реально возвращённые байты.
ОшибкиОтсутствие метода E_WARNING; лишние байты сверх count предупреждаются и теряются.
Два уровня буферизации
  1. fread просит часть данных
  2. PHP при необходимости запрашивает wrapper
  3. stream_read возвращает не больше count
  4. PHP отдаёт нужное, остаток может хранить в буфере

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

read возвращает максимум count байтов и продвигает внутреннюю позицию на их фактическое количество. Пустая строка означает отсутствие доступных байтов; false — отказ чтения. Внешний fread(3) не обещает вызов stream_read(3): PHP может запросить больше и сохранить остаток во внутреннем буфере.

Читать результат по частямСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try { echo fread($stream, 3), "\n"; echo stream_get_contents($stream); }
        finally { fclose($stream); }
Ожидаемый результат
tea

milk

Первый вызов получает три байта, затем остаток с переводом строки. Приложение видит правильную последовательность, хотя число callback-вызовов может отличаться от числа fread.

Показать, что пустой список заказов успешно прочитанСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/empty.txt', 'rb');
        if ($stream === false) { throw new RuntimeException('Список недоступен'); }
        try {
            $data = fread($stream, 100);
            if ($data === false) { throw new RuntimeException('Ошибка чтения'); }
            echo $data === '' ? "Заказов на выдачу пока нет.\n" : $data;
        } finally { fclose($stream); }
Ожидаемый результат
Заказов на выдачу пока нет.

Callback вернул успешную пустую строку. false и пустота требуют разных действий: исправить чтение либо показать пустой список. Временная пустота живого non-blocking соединения имеет другой договор.

Связанные операции: streamWrapper::stream_eof, streamWrapper::stream_seek, stream_get_contents.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_writeЗаписать доступное количество входных байтов и вернуть точное число принятых.

PHP 4.3.2+ · standard

streamWrapper::stream_write(string $data): int

Записать доступное количество входных байтов и вернуть точное число принятых.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
dataСтрока байтов; не обещанная целая строка/HTTP-запрос.
ВозвратКоличество записанных байтов от 0 до strlen(data); 0 если ничего не записано.
ПозицияПродвинуть на фактически записанные байты, соблюдать режим a при реализации append.
ОшибкиОтсутствие метода E_WARNING; завышенный результат предупреждается и обрезается.
Учёт записи
  1. Вход содержит N байтов
  2. Хранилище принимает K ≤ N
  3. Позиция сдвигается на K
  4. write возвращает K, вызывающий обрабатывает остаток

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

write принимает строку байтов и возвращает число действительно записанных. int здесь не произвольный код успеха: true или 1 вместо длины сообщает запись одного байта. При ограниченном источнике разрешена частичная запись; вызывающему коду нужно продолжить с оставшейся части, не дублируя уже записанную.

Сохранить состояние заказа через привычный fwriteСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/result.txt', 'wb');
        if ($stream === false) { throw new RuntimeException('Не открыть результат'); }
        try {
            $text = "104:ready\n";
            if (fwrite($stream, $text) !== strlen($text)) { throw new RuntimeException('Запись неполная'); }
        } finally { fclose($stream); }
        echo file_get_contents(__DIR__ . '/store/result.txt');
Ожидаемый результат
104:ready

Внешний fwrite передаёт строку в stream_write; метод возвращает число принятых байтов, не произвольный true. Повторное чтение напрямую из файла подтверждает результат адаптера. При неполной записи этот пример завершается ошибкой, не обещая отката.

Перезаписать часть существующих байтовСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'r+b');
        try {
            fseek($stream, 4);
            echo fwrite($stream, 'silk'), "\n";
            rewind($stream);
            echo stream_get_contents($stream);
        } finally { fclose($stream); }
Ожидаемый результат
4
tea
silk

r+b сохраняет файл, seek выбирает позицию, write заменяет четыре байта. Запись внутри файла не вставляет автоматически новый участок со сдвигом хвоста.

Если ничего сохранить нельзя, возвращают 0. Завышенное число больше strlen(data) вызывает warning и ограничивается длиной входа; это баг wrapper, а не механизм подтверждения доставки. Наш мост переводит false внутреннего fwrite в 0 и оставляет его warning; production-адаптер должен описать собственную диагностику и частичный результат. Успешная запись не означает fsync, получение другой стороной или фиксацию бизнес-транзакции.

Связанные операции: streamWrapper::stream_open, streamWrapper::stream_flush, streamWrapper::stream_truncate.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_seekИзменить позицию доступного для перемотки источника относительно начала, текущей позиции или конца.

PHP 4.3.2+ · standard

streamWrapper::stream_seek(int $offset, int $whence): bool

Изменить позицию доступного для перемотки источника относительно начала, текущей позиции или конца.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
offsetСмещение в байтах, знак зависит от выбранной базы.
whenceSEEK_SET / SEEK_CUR / SEEK_END; текущий PHP нормализует CUR в SET.
Возвратtrue позиция обновлена; false не удалось или метод не реализован.
Связь с tellПосле успеха PHP уточняет позицию через stream_tell; буферные seek могут не вызывать callback.
Перемотка без записи
  1. Выбрать базовую позицию
  2. Добавить offset и проверить допустимость
  3. Переместить внутренний источник
  4. tell подтверждает действительную позицию

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

seek меняет позицию, а не содержимое. offset измеряется в байтах. SEEK_SET отсчитывает от начала, SEEK_CUR от текущей позиции, SEEK_END от конца; отрицательный offset допустим относительно конца/текущего, пока итоговая позиция допустима для источника.

Перейти к началу второго словаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try { var_dump(fseek($stream, 4)); echo fread($stream, 4), "\n"; }
        finally { fclose($stream); }
Ожидаемый результат
int(0)
milk

Внешний fseek возвращает 0 при успехе, хотя callback stream_seek возвращает true. Это разные уровни контракта; нельзя вернуть 0 из callback по аналогии с fseek.

Отсчитать позицию от конца файлаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try { fseek($stream, -5, SEEK_END); echo stream_get_contents($stream); }
        finally { fclose($stream); }
Ожидаемый результат
milk

Конец файла находится после завершающего перевода строки. -5 возвращает к milk. Для UTF-8 число символов и байтов различается: случайное смещение может попасть внутрь символа.

В текущей реализации PHP преобразует SEEK_CUR в SEEK_SET перед callback; корректная общая реализация всё же описывает все три режима. Некоторые перемещения движок выполняет внутри собственного read-буфера, не вызывая wrapper. После успешного stream_seek PHP запрашивает stream_tell; отказ определения позиции превращает операцию в неуспех. Источник без произвольного доступа должен вернуть false, а не симулировать перемещение счётчиком.

Связанные операции: streamWrapper::stream_tell, streamWrapper::stream_read, stream_set_read_buffer.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_tellВернуть текущую внутреннюю позицию источника для согласования операций seek.

PHP 4.3.2+ · standard

streamWrapper::stream_tell(): int

Вернуть текущую внутреннюю позицию источника для согласования операций seek.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
ВозвратТекущая позиция источника в байтах; должен быть настоящий int.
ГраницаВнешний ftell может учитывать PHP read-буфер; не заменяйте позицию числом callback-вызовов.
Позиция источника и читателя
  1. Источник имеет собственный offset
  2. PHP может держать непрочитанный буфер
  3. tell сообщает offset источника
  4. Внешний ftell учитывает состояние PHP

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

tell сообщает внутреннюю текущую позицию wrapper в байтах. PHP использует её при синхронизации перемотки. Внешний ftell учитывает также свой буфер; не вычисляйте возвращаемую позицию по числу вызовов read, потому что чтение может быть упреждающим.

Продолжить чтение с сохранённой позиции заказаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/queue.txt', 'rb');
        try {
            echo 'Первый заказ: ', rtrim(fgets($stream)), "\n";
            $next = ftell($stream);
            echo 'Позиция следующего: ', $next, "\n";
            rewind($stream);
            if (fseek($stream, $next) !== 0) { throw new RuntimeException('Не восстановить позицию'); }
            echo 'Продолжение: ', rtrim(fgets($stream)), "\n";
        } finally { fclose($stream); }
Ожидаемый результат
Первый заказ: 104
Позиция следующего: 4
Продолжение: 105

ftell снаружи учитывает PHP-буфер; callback stream_tell сообщает позицию своего источника. Смещение4 относится к байтам неизменённого файла. Это демонстрация продолжения, не надёжная многопользовательская очередь.

Различить позицию читателя и чтение вперёдСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try { echo fread($stream, 3), ':', ftell($stream), "\n"; rewind($stream); echo ftell($stream), "\n"; }
        finally { fclose($stream); }
Ожидаемый результат
tea:3
0

Снаружи прочитано три байта, значит позиция приложения 3, даже если внутренний файл wrapper уже прочитан PHP на большее расстояние. rewind возвращает читателя к началу.

Связанные операции: streamWrapper::stream_seek, streamWrapper::stream_read.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_eofСообщить настоящий конец источника без потребления данных.

PHP 4.3.2+ · standard

streamWrapper::stream_eof(): bool

Сообщить настоящий конец источника без потребления данных.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
Возвратtrue достигнут конец и данных больше нет; false иначе.
СвязьPHP вызывает после stream_read и при feof; корректность зависит от состояния underlying stream.
ГраницаВременно пусто не равно завершено; непустой read при чтении целиком перевешивает EOF.
Проверка конца
  1. Чтение изменило позицию
  2. Wrapper узнаёт состояние источника
  3. EOF true только при завершении
  4. Перемотка может снова разрешить чтение

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

eof отвечает на вопрос, достигнут ли конец и больше ли нет данных. Он не должен пытаться потребить следующий байт как побочный эффект. Для файла это конец; для живого соединения временно пустой буфер ещё не конец связи.

Дойти до конца файлаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try { var_dump(feof($stream)); echo stream_get_contents($stream); var_dump(feof($stream)); }
        finally { fclose($stream); }
Ожидаемый результат
bool(false)
tea
milk
bool(true)

До чтения конец ещё не обнаружен; чтение до исчерпания источника приводит к EOF. Сам feof не обязан заранее предсказывать размер будущей записи.

Вернуться от конца к чтениюСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try { stream_get_contents($stream); rewind($stream); var_dump(feof($stream)); echo fread($stream, 3), "\n"; }
        finally { fclose($stream); }
Ожидаемый результат
bool(false)
tea

После успешной перемотки конец больше не является текущим состоянием. Собственный in-memory wrapper должен так же согласовать position и EOF; постоянный true после первого конца сломает повторное чтение.

Связанные операции: streamWrapper::stream_read, streamWrapper::stream_seek, stream_select.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_truncateИзменить размер открытого хранилища по запросу ftruncate.

PHP 5.4+ · standard

streamWrapper::stream_truncate(int $new_size): bool

Изменить размер открытого хранилища по запросу ftruncate.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
new_sizeНовый размер в байтах; обычный вызывающий ftruncate требует неотрицательное значение.
Возвратtrue размер изменён; false невозможно.
ПозицияИзменение длины не обязано перемещать текущий cursor.
ГраницаТребуется доступ на запись и поддержка источника; файл может потерять хвост без возможности отката.
Изменение размера
  1. ftruncate запрашивает новую длину
  2. Wrapper проверяет допустимость
  3. Хранилище укорачивается или расширяется
  4. Позиция отдельно от размера

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

truncate меняет размер хранилища. Уменьшение отбрасывает хвост; увеличение для обычного файла добавляет нулевые байты. Это не то же самое, что перемещение курсора: прежняя позиция может остаться за новым концом. Реализация своего хранилища обязана явно обеспечить выбранную семантику.

Удалить хвост старого, более длинного статусаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/status.txt', 'r+b');
        try {
            $status = 'ready';
            if (fwrite($stream, $status) !== strlen($status)) { throw new RuntimeException('Неполная запись'); }
            if (!ftruncate($stream, strlen($status))) { throw new RuntimeException('Не удалить старый хвост'); }
            rewind($stream); echo stream_get_contents($stream), "\n";
        } finally { fclose($stream); }
Ожидаемый результат
ready

r+b сохраняет старое содержимое. Без truncate после замены processing на ready осталось бы readyssing. Callback меняет длину, а rewind отдельно меняет позицию. Для обычной полной замены файла режим wb проще.

Увеличить файл и показать нулевой хвостСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/small.txt', 'r+b');
        try { ftruncate($stream, 5); rewind($stream); echo bin2hex(stream_get_contents($stream)), "\n"; }
        finally { fclose($stream); }
Ожидаемый результат
7465610000

Fixture содержит tea (74 65 61 в hex). Локальный filesystem дополнил два NUL. Для удалённого или виртуального хранилища это нужно реализовать или честно вернуть false; не обещайте операции только потому, что метод объявлен.

Связанные операции: streamWrapper::stream_write, streamWrapper::stream_stat, streamWrapper::stream_seek.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::__constructПодготовить независимый объект обработчика перед операцией над адресом.

PHP 4.3.2+ · standard

streamWrapper::__construct()

Подготовить независимый объект обработчика перед операцией над адресом.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыPHP не передаёт пользовательские аргументы; объявление с обязательными параметрами несовместимо.
НазначениеИнициализация состояния, не открытие произвольного URL из несуществующего аргумента.
МоментПеред stream_open при открытии; объекты также создаются для других URL callbacks.
От реестра к экземпляру
  1. register хранит имя класса
  2. Операция обращается к схеме
  3. PHP создаёт отдельный объект без аргументов
  4. Конкретный callback получает путь/режим

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

В отличие от php_user_filter, обычный wrapper получает вызов __construct. PHP создаёт объект перед stream_open, не передаёт в конструктор аргументы fopen и только затем просит открыть конкретный адрес. Поэтому URL и mode обрабатывают в stream_open, а в конструкторе готовят нейтральное состояние.

Наблюдать создание объекта перед открытиемСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        echo implode(',', array_slice(LessonFileBridge::$events, 0, 2)), "\n";
        fclose($stream);
Ожидаемый результат
construct,open

Журнал принадлежит учебному классу. Сначала вызван конструктор без аргументов, затем open с конкретным адресом. Регистрация имени сама по себе не создавала объект.

Получить отдельное состояние для двух открытийСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $first = fopen('lesson://store/tea.txt', 'rb');
        $second = fopen('lesson://store/tea.txt', 'rb');
        try {
            echo count(array_filter(LessonFileBridge::$events, fn($event) => $event === 'construct')), "\n";
            echo fread($first, 3), ':', fread($second, 3), "\n";
        } finally { fclose($first); fclose($second); }
Ожидаемый результат
2
tea:tea

Два объекта хранят два независимых файловых ресурса и две позиции. Не храните общий cursor в static-свойстве: тогда чтение одного посетителя влияло бы на другое открытие.

Не требуйте обязательный аргумент конструктора: движок вызывает его без параметров. Контекст доступен публичному свойству wrapper по правилам операции; параметры пути надёжнее разбирать при открытии. Конструктор также может появиться ради url_stat/unlink и других операций по URL — объект не обязательно станет долгоживущим открытым файлом.

Связанные операции: stream_wrapper_register, streamWrapper::stream_open, php_user_filter::onCreate, streamWrapper::__destruct.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::__destructЗавершить жизнь объекта wrapper, различая её с закрытием самого потока.

PHP 4.3.2+ · standard

streamWrapper::__destruct()

Завершить жизнь объекта wrapper, различая её с закрытием самого потока.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
Аргументы / возвратАргументов нет; возвращаемое значение не используется.
МоментКогда объект освобождается; внешняя ссылка может отложить после fclose.
Версионная сверкаВ PHP 8.5.11 source и опыт показывают flush → close → destruct при обычном закрытии записанного файла, вопреки краткой странице.
НазначениеФинальная уборка объекта, не гарантия выполнения бизнес-действия.
Два времени жизни
  1. fclose завершает stream resource
  2. stream_close освобождает внутренний файл
  3. Внешняя ссылка может держать объект
  4. Последняя ссылка исчезает — destruct

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

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

Проверить реальный порядок закрытия в PHP 8.5.11Самостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/result.txt', 'wb');
        fwrite($stream, 'tea');
        fclose($stream);
        $events = LessonFileBridge::$events;
        var_dump(array_search('flush', $events, true) < array_search('close', $events, true));
        var_dump(array_search('close', $events, true) < array_search('destruct', $events, true));
Ожидаемый результат
bool(true)
bool(true)

На проверенной версии финальная запись вызывает flush, затем stream_close, затем освобождение объекта и __destruct. Краткая официальная страница утверждает «destruct перед flush»; это расходится с tagged source 8.5.11 и данным опытом, поэтому здесь не выдаётся за контракт.

Удержать объект после закрытия ресурсаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        $wrapper = stream_get_meta_data($stream)['wrapper_data'];
        fclose($stream);
        var_dump(in_array('close', LessonFileBridge::$events, true));
        var_dump(in_array('destruct', LessonFileBridge::$events, true));
        unset($wrapper);
        var_dump(in_array('destruct', LessonFileBridge::$events, true));
Ожидаемый результат
bool(true)
bool(false)
bool(true)

wrapper_data у пользовательского потока содержит объект. Переменная удерживает его, но внутренний файл уже закрыт stream_close. unset последней ссылки допускает деструктор. У других wrappers wrapper_data имеет другой контракт, например массив HTTP-заголовков.

Связанные операции: streamWrapper::stream_close, streamWrapper::stream_flush, stream_get_meta_data.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_closeОсвободить ресурсы конкретного открытого потока при fclose.

PHP 4.3.2+ · standard

streamWrapper::stream_close(): void

Освободить ресурсы конкретного открытого потока при fclose.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет; используется состояние экземпляра.
Возвратvoid, значение не передаётся вызывающему коду как результат работы.
ОбязанностьОсвободить внутренние ресурсы и блокировки; для directory handle существует dir_closedir.
Закрытие ресурса
  1. Приложение вызывает fclose
  2. PHP завершает необходимые записи
  3. stream_close освобождает handles
  4. Внешний stream становится закрытым

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

close вызывается для закрытия открытого stream resource. Освободите внутренние файлы, временные handles и удержанные блокировки. Метод ничего не возвращает; завершать данные следует раньше в stream_flush, если у wrapper есть буфер. Полагаться только на автоматическую уборку в конце запроса неудобно для долгих workers.

Завершить запись, сохранив созданный документСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/pickup.txt', 'wb');
        fwrite($stream, "104:ready\n");
        fclose($stream);
        echo is_resource($stream) ? "Ресурс ещё открыт\n" : "Открытый ресурс закрыт\n";
        echo 'Файл сохранён: ', file_get_contents(__DIR__ . '/store/pickup.txt');
Ожидаемый результат
Открытый ресурс закрыт
Файл сохранён: 104:ready

stream_close освобождает внутренний handle данного экземпляра. Закрыть ресурс не значит удалить документ. Повторно читать можно после нового открытия, не из закрытого ресурса.

Закрытие одного открытия не закрывает второеСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $first = fopen('lesson://store/tea.txt', 'rb');
        $second = fopen('lesson://store/tea.txt', 'rb');
        fclose($first);
        try { echo stream_get_contents($second); }
        finally { fclose($second); }
Ожидаемый результат
tea
milk

Внутренний handle принадлежит экземпляру. Общая static-переменная для открытого файла нарушила бы эту независимость.

Связанные операции: streamWrapper::__destruct, streamWrapper::stream_flush, streamWrapper::dir_closedir.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_flushСбросить собственный кэш wrapper в нижележащее хранилище.

PHP 4.3.2+ · standard

streamWrapper::stream_flush(): bool

Сбросить собственный кэш wrapper в нижележащее хранилище.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
Возвратtrue данные сохранены или кэш пуст; false не удалось либо callback не реализован.
Триггерыfflush, а также закрытие с несброшенными записями.
ГраницаНе универсальная гарантия физической долговечности, сетевой доставки или commit.
Сброс одного уровня
  1. write мог положить байты в кэш
  2. fflush вызывает stream_flush
  3. Wrapper передаёт кэш своему источнику
  4. true подтверждает только этот договор

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

Если wrapper принял байты в собственный кэш, fflush просит передать их в нижележащее хранилище. true означает, что кэш сброшен либо сбрасывать нечего. Это не автоматическая гарантия fsync, доставки по сети или транзакционной фиксации: смысл зависит от вашего хранилища.

Сбросить запись перед объявлением отчёта готовымСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/report.txt', 'wb');
        try {
            $text = "104:ready\n";
            if (fwrite($stream, $text) !== strlen($text) || !fflush($stream)) {
                throw new RuntimeException('Нельзя объявить отчёт готовым');
            }
            echo "Запись передана нижнему хранилищу.\n";
        } finally { fclose($stream); }
        echo file_get_contents(__DIR__ . '/store/report.txt');
Ожидаемый результат
Запись передана нижнему хранилищу.
104:ready

fflush вызывает stream_flush. Учебный мост делегирует сброс реальному локальному файлу; это не обещание физической сохранности при отключении питания, fsync или транзакции.

Не считать пустой flush ошибкойСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/empty.txt', 'wb');
        try { var_dump(fflush($stream)); echo fstat($stream)['size'], "\n"; }
        finally { fclose($stream); }
Ожидаемый результат
bool(true)
0

Нечего дописывать — нормальный успешный случай. Собственный wrapper с сетью/буфером должен вернуть false при неудачной передаче, не игнорируя её ради такого же вывода.

PHP вызывает callback при fflush и при закрытии, когда были несброшенные записи. Если метод не реализован, предполагается false. Ошибку финализации лучше обнаруживать явным fflush до объявления экспорта готовым; stream_close не возвращает отдельный boolean вашей бизнес-логике.

Связанные операции: streamWrapper::stream_write, streamWrapper::stream_close, stream_set_write_buffer.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_statСообщить сведения stat для уже открытого stream resource.

PHP 4.3.2+ · standard

streamWrapper::stream_stat(): array|false

Сообщить сведения stat для уже открытого stream resource.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет; сведения относятся к внутреннему состоянию экземпляра.
ВозвратМассив формата stat либо false при отказе.
Критичные поляmode с типом/режимом и size; недоступные значения согласованно заполнены.
Отсутствие методаE_WARNING при запросе сведений.
Информация открытого ресурса
  1. fstat получает stream resource
  2. PHP вызывает stream_stat его объекта
  3. Wrapper читает метаданные своего источника
  4. Возвращается массив stat

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

fstat спрашивает сведения уже открытого экземпляра: размер, тип, времена и остальные поля stat. Wrapper должен вернуть массив в формате stat, а не только строку размера. Если данных нет, возвращают false; неизвестные поля заполняют осмысленно, обычно нулём, а не случайными значениями.

Проверить размер листа перед чтениемСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/pickup.txt', 'rb');
        try {
            $info = fstat($stream);
            if ($info === false || $info['size'] > 100) { throw new RuntimeException('Нужен порционный читатель'); }
            echo 'Размер: ', $info['size'], " байт\n";
            echo stream_get_contents($stream, 101);
        } finally { fclose($stream); }
Ожидаемый результат
Размер: 10 байт
104:ready

stream_stat возвращает полный stat-массив реального открытого файла. size помогает выбрать стратегию; оно может измениться в конкурентном сценарии, поэтому чтение также ограничено. Биты mode и все остальные поля объяснены ниже.

Проверить размер после записи и изменения длиныСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/report.txt', 'w+b');
        try { fwrite($stream, 'tea'); echo fstat($stream)['size'], "\n"; ftruncate($stream, 1); echo fstat($stream)['size'], "\n"; }
        finally { fclose($stream); }
Ожидаемый результат
3
1

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

Поля statСмысл
dev, inoУстройство и inode/идентификатор, когда хранилище их имеет.
mode, nlinkТип/режим и число ссылок; mode важен для is_file/is_dir.
uid, gid, rdevВладелец, группа, устройство специального файла; смысл зависит от платформы.
sizeРазмер в байтах.
atime, mtime, ctimeВремена доступа, изменения содержимого и изменения метаданных; ctime не универсальное время создания.
blksize, blocksРазмер блока I/O и число выделенных блоков, где поддержано.

Обычный stat содержит числовые индексы 0–12 и соответствующие имена. Для виртуального источника следуйте этому формату и ограничениям платформы. Отсутствующий stream_stat вызывает E_WARNING; не скрывайте его фиктивным успешным массивом, если библиотеки действительно опираются на эти сведения.

Связанные операции: streamWrapper::url_stat, streamWrapper::stream_truncate, stream_get_meta_data.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::url_statПолучить stat-сведения по адресу, включая тихие проверки существования и сведения о ссылке.

PHP 4.3.2+ · standard

streamWrapper::url_stat(string $path, int $flags): array|false

Получить stat-сведения по адресу, включая тихие проверки существования и сведения о ссылке.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
pathURL только с ://, который требуется проверить.
flagsМаска STREAM_URL_STAT_LINK и STREAM_URL_STAT_QUIET.
ВозвратПолный массив формата stat; false при отсутствии/отказе.
ОшибкиQUIET не должен порождать сообщения; без QUIET wrapper сообщает отказ. Отсутствующий метод вызывает E_WARNING.
Один callback для многих проверок
  1. file_exists/is_file/filesize получают URL
  2. PHP вызывает url_stat с флагами
  3. Wrapper возвращает mode, size и остальные поля
  4. PHP выводит нужное свойство

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

url_stat проверяет адрес без предварительного fopen. Он нужен не только stat: file_exists, is_file, is_dir, filesize, filemtime и множество операций SplFileInfo используют тот же протокол. Поэтому read-only wrapper без url_stat может хорошо читаться и одновременно не проходить проверку существования в библиотеке.

Найти готовый документ через URL без ручного открытияСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $url = 'lesson://store/pickup.txt';
        if (!is_file($url)) { throw new RuntimeException('Нет готового листа выдачи'); }
        echo 'Лист, ', filesize($url), " байт:\n", file_get_contents($url);
Ожидаемый результат
Лист, 10 байт:
104:ready

is_file и filesize используют url_stat. Callback нужен даже адаптеру, который умеет только читать: библиотеки часто проверяют путь до открытия. Проверка существования не исключает последующий отказ чтения.

Проверить отсутствие без сообщения пользователюСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $reported = 0;
        set_error_handler(function ($level) use (&$reported) {
            if (error_reporting() & $level) { $reported++; }
            return true;
        });
        try { var_dump(file_exists('lesson://store/missing.txt')); }
        finally { restore_error_handler(); }
        echo $reported, "\n";
Ожидаемый результат
bool(false)
0

file_exists передаёт тихую проверку. Wrapper подавляет внутренний stat warning и не создаёт собственное сообщение при QUIET. Handler учитывает error_reporting: пользовательский handler вызывается и для подавленных ошибок, поэтому сам факт его вызова ещё не публичная диагностика.

STREAM_URL_STAT_LINK просит сведения о самой ссылке, не о её цели (lstat/is_link/filetype). STREAM_URL_STAT_QUIET запрещает wrapper сообщать ошибку; без флага он отвечает за корректную диагностику. Context обновляется при его передаче вызывающей операции. PHP может кэшировать stat: после изменения пути в тесте очищайте clearstatcache; кэш не должен изображать реальное отсутствие гонок между проверкой и открытием.

Связанные операции: streamWrapper::stream_stat, streamWrapper::stream_open, stream_context_get_options.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_castПредоставить PHP внутренний ресурс, пригодный для запрошенного низкоуровневого действия.

PHP 5.3+ · standard

streamWrapper::stream_cast(int $cast_as): resource|false

Предоставить PHP внутренний ресурс, пригодный для запрошенного низкоуровневого действия.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
cast_asSTREAM_CAST_FOR_SELECT или STREAM_CAST_AS_STREAM.
ВозвратВнутренний stream resource либо false; не int и не внешний ресурс самого wrapper.
ГраницаТребуется поддержка операции underlying транспортом/ОС; наличие метода не создаёт такую поддержку.
Обёртка раскрывает ресурс для ОС
  1. select видит пользовательский поток
  2. PHP просит stream_cast(FOR_SELECT)
  3. Wrapper возвращает underlying socket
  4. ОС проверяет его готовность

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

Не каждую абстракцию можно передать системному select. stream_cast позволяет вернуть настоящий внутренний stream resource, для которого ОС умеет ожидать готовность. Вернуть произвольный целый descriptor или сам внешний wrapper нельзя. Если подходящего ресурса нет, честный ответ — false.

Получить внутренний ресурс напрямую для наблюденияСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $object = new LessonFileBridge();
        $opened = null;
        $object->stream_open('lesson://store/tea.txt', 'rb', 0, $opened);
        try {
            $inner = $object->stream_cast(STREAM_CAST_AS_STREAM);
            echo get_resource_type($inner), ':', fread($inner, 3), "\n";
        } finally { $object->stream_close(); }
Ожидаемый результат
stream:tea

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

Дать stream_select настоящий TCP-ресурс за wrapperСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        require __DIR__ . '/tcp-lab.php';
        [$client, $peer] = lessonTcpPair();
        class SocketBridge {
            public $context = null;
            public static $inner;
            public function stream_open($path, $mode, $options, &$opened): bool { return is_resource(self::$inner); }
            public function stream_cast($castAs) { return self::$inner; }
            public function stream_read($count): string|false { return fread(self::$inner, $count); }
            public function stream_eof(): bool { return feof(self::$inner); }
            public function stream_close(): void { fclose(self::$inner); }
        }
        SocketBridge::$inner = $client;
        stream_wrapper_register('castlab', SocketBridge::class);
        $wrapped = fopen('castlab://pair', 'rb');
        try {
            lessonWriteAll($peer, 'tea');
            $read = [$wrapped]; $write = null; $except = null;
            echo stream_select($read, $write, $except, 1), "\n";
            echo lessonReadExact($wrapped, 3), "\n";
        } finally { fclose($wrapped); fclose($peer); stream_wrapper_unregister('castlab'); }
Ожидаемый результат
1
tea

Это реальная ограниченная TCP-лаборатория только на 127.0.0.1. Статический inner допустим лишь для единственного подготовленного соединения опыта; production-wrapper хранит ресурс в каждом экземпляре и определяет владение.

cast_as равен STREAM_CAST_FOR_SELECT при ожидании готовности или STREAM_CAST_AS_STREAM для иных применений. Возвращаемый underlying resource должен поддерживать нужное действие; обычный файл на Windows нельзя считать пригодным для socket select. Краткая таблица констант ошибочно называет streamWrapper::stream_select — такого callback нет: вызывает глобальная stream_select.

Связанные операции: stream_select, stream_socket_client, streamWrapper::stream_close.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_lockВыполнить настоящую совместимую блокировку источника либо честно отказать.

PHP 5+ · standard

streamWrapper::stream_lock(int $operation): bool

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

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
operationLOCK_SH / LOCK_EX / LOCK_UN; бит LOCK_NB просит не ждать.
Возвратtrue операция выполнена, false отказ/неподдерживаемая блокировка.
Триггерыflock, file_put_contents с LOCK_EX; освобождение при завершении зависит от владельца underlying ресурса.
ОшибкиОтсутствующий метод может дать E_WARNING; платформенные возможности проверяются для выбранного backend.
Блокировка вокруг общего файла
  1. Первый handle получает EX
  2. Второй EX|NB видит занятость
  3. Первый освобождает UN
  4. Второй получает блокировку

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

Блокировка имеет смысл только тогда, когда все конкурирующие участники используют совместимый механизм. Нельзя реализовать stream_lock как return true без настоящей блокировки: file_put_contents(..., LOCK_EX) тогда создаёт ложную уверенность в защите параллельной записи.

Изменить остаток в одной защищённой операцииСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/stock.txt', 'r+b');
        try {
            if (!flock($stream, LOCK_EX)) { throw new RuntimeException('Остаток занят'); }
            try {
                $stock = (int) stream_get_contents($stream);
                if ($stock < 2) { throw new RuntimeException('Недостаточно товара'); }
                $next = (string) ($stock - 2);
                rewind($stream);
                if (fwrite($stream, $next) !== strlen($next) || !ftruncate($stream, strlen($next)) || !fflush($stream)) {
                    throw new RuntimeException('Остаток не сохранён');
                }
            } finally { flock($stream, LOCK_UN); }
        } finally { fclose($stream); }
        echo 'После выдачи двух: ', file_get_contents('lesson://store/stock.txt'), "\n";
Ожидаемый результат
После выдачи двух: 10

stream_lock должен действительно делегировать flock. Блокируется вся операция прочитать→изменить→записать. Фикстура содержит проверенное целое12; рабочий импорт дополнительно валидирует формат, а БД обычно удобнее для заказов и нескольких связанных остатков.

Получить отказ немедленной блокировки другого открытияСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $first = fopen('lesson://store/tea.txt', 'r+b');
        $second = fopen('lesson://store/tea.txt', 'r+b');
        try {
            flock($first, LOCK_EX);
            var_dump(flock($second, LOCK_EX | LOCK_NB));
            flock($first, LOCK_UN);
            var_dump(flock($second, LOCK_EX | LOCK_NB));
            flock($second, LOCK_UN);
        } finally { fclose($first); fclose($second); }
Ожидаемый результат
bool(false)
bool(true)

На проверенной Windows/PHP 8.5.11 два открытия конкурируют: NB не ждёт, после освобождения succeeds. На странице wrapper есть историческое обобщение «NB не поддерживается Windows»; оно не описывает этот подтверждённый local-file backend. Другой transport/FS может отличаться.

operation содержит LOCK_SH, LOCK_EX или LOCK_UN, иногда вместе с LOCK_NB. Не делайте сравнение только ===LOCK_EX, забыв снять/проверить NB. Callback связан с flock и записью с LOCK_EX; закрытие освобождает удержанное состояние underlying ресурса. Современный stream_set_blocking идёт через stream_set_option, несмотря на старое упоминание в описании stream_lock. Advisory lock не защищает от кода, который её игнорирует, и не равен транзакции БД.

Связанные операции: stream_supports_lock, streamWrapper::stream_close, streamWrapper::stream_set_option.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_set_optionПеревести запрос blocking, таймаута или буфера в настройку underlying ресурса.

PHP 5.3+ · standard

streamWrapper::stream_set_option(int $option, int $arg1, ?int $arg2): bool

Перевести запрос blocking, таймаута или буфера в настройку underlying ресурса.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
optionОдна из четырёх STREAM_OPTION_* операций из таблицы.
arg1 / arg2Смысл определяется option; BLOCKING передаёт null вторым значением в PHP 8.5.11.
Возвратtrue поддержанная настройка принята; false отказ/неизвестная опция.
Версия / типМетод с PHP 5.3; nullable arg2 выбран по фактическому callback PHP 8.5.11, с объяснением расхождения synopsis.
Настройка проходит через адаптер
  1. stream_set_* формирует option и значения
  2. PHP вызывает stream_set_option
  3. Wrapper настраивает внутренний ресурс
  4. Успех не выдумывается при отказе

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

Этот callback переводит общую настройку потока в возможности конкретного источника. Не возвращайте true просто потому, что узнали имя option: нижний ресурс должен принять соответствующую настройку. Буфер, таймаут и blocking — разные вещи.

Увидеть null вместо неиспользуемого arg2Самостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try {
            stream_set_blocking($stream, true);
            var_dump(in_array('option:' . STREAM_OPTION_BLOCKING . ':null', LessonFileBridge::$events, true));
        } finally { fclose($stream); }
Ожидаемый результат
bool(true)

В PHP 8.5.11 arg2 для BLOCKING действительно null: это подтверждено исходником userspace.c и выполнением. Поэтому лаборатория объявляет ?int $arg2, хотя synopsis страницы пишет int. Для пользовательского callback слепое копирование более узкого типа вызвало бы TypeError.

Передать запрос отключить read-буферСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $stream = fopen('lesson://store/tea.txt', 'rb');
        try {
            var_dump(stream_set_read_buffer($stream, 0));
            echo fread($stream, 3), "\n";
        } finally { fclose($stream); }
Ожидаемый результат
int(0)
tea

Внешняя функция возвращает 0 при принятой настройке, wrapper callback — true. Отключение буфера одного уровня не означает отсутствия любых буферов ОС/протокола.

optionarg1 / arg2
STREAM_OPTION_BLOCKING1 блокировать или 0 не блокировать; arg2 не используется, в 8.5.11 null.
STREAM_OPTION_READ_TIMEOUTСекунды / микросекунды таймаута чтения.
STREAM_OPTION_READ_BUFFERSTREAM_BUFFER_NONE или STREAM_BUFFER_FULL / требуемый размер.
STREAM_OPTION_WRITE_BUFFERSTREAM_BUFFER_NONE или STREAM_BUFFER_FULL / требуемый размер.

STREAM_BUFFER_LINE существует в общем наборе констант, но описанные пользовательские setters не дают обещания универсального line-buffering. Неизвестный/неподдерживаемый option возвращает false. Таймаут нужен сетевому ожиданию; обычный локальный файл не следует выдавать за сервер, который реально соблюдает такую сетевую политику.

Связанные операции: stream_set_blocking, stream_set_timeout, stream_set_read_buffer, stream_set_write_buffer.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::stream_metadataИзменить время, режим или владельца по URL согласно конкретному option.

PHP 5.4+ · standard

streamWrapper::stream_metadata(string $path, int $option, mixed $value): bool

Изменить время, режим или владельца по URL согласно конкретному option.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
pathURL вида scheme://… для изменения.
optionSTREAM_META_TOUCH/OWNER_NAME/OWNER/GROUP_NAME/GROUP/ACCESS.
valueМассив времён, имя, числовой ID или mode в зависимости от option.
Возвратtrue изменение выполнено; false ошибка либо операция не реализована.
ПлатформаНекоторые операции недоступны на данной ОС/FS или с текущими правами.
Один callback, разные операции
  1. touch/chmod/chown/chgrp передают URL
  2. option определяет вид изменения
  3. value проверяется по этому виду
  4. Хранилище принимает либо отвергает действие

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

metadata меняет сведения по URL: время, права или владельца. Открытый файл для этого не обязателен. option определяет тип value; нельзя обрабатывать любое значение как одинаковый массив. Неподдерживаемая операция должна возвращать false, не имитировать административное действие.

Отметить известное время готовности учебного отчётаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $url = 'lesson://store/tea.txt';
        if (!touch($url, 1700000000, 1700000000)) { throw new RuntimeException('Не изменить время'); }
        clearstatcache();
        echo 'Готов с: ', gmdate('Y-m-d H:i:s', filemtime($url)), " UTC\n";
        echo file_get_contents($url);
Ожидаемый результат
Готов с: 2023-11-14 22:13:20 UTC
tea
milk

STREAM_META_TOUCH передаёт два Unix-времени: изменения и доступа. Изменение метаданных не переписывает содержимое; UTC делает результат независимым от часового пояса. Это заданная учебная дата.

Честно отклонить изменение владельца в ограниченном адаптереСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $object = new LessonFileBridge();
        var_dump($object->stream_metadata('lesson://store/tea.txt', STREAM_META_OWNER_NAME, 'demo-owner'));
        var_dump($object->stream_metadata('lesson://store/tea.txt', STREAM_META_GROUP, 123));
Ожидаемый результат
bool(false)
bool(false)

Прямой вызов показывает ветви договора без реального изменения OS-владельцев. Лаборатория умышленно поддерживает только touch/chmod; она не заявляет, что chown/chgrp всегда доступны в Windows или под любым пользователем.

optionvalue
STREAM_META_TOUCHМассив [mtime, atime] от touch; без явно переданных времён возможен пустой массив — используйте текущие.
STREAM_META_OWNER_NAME / GROUP_NAMEСтроковое имя владельца / группы.
STREAM_META_OWNER / GROUPЦелый идентификатор владельца / группы.
STREAM_META_ACCESSЦелый mode от chmod.

URL должен иметь ://; PHP обновляет context при его передаче. Времена/права/владельцы зависят от ОС и хранилища. Например chmod под Windows не создаёт полноценную POSIX-модель разрешений. После успешного изменения учитывайте stat-кэш; успех setter не делает проверку доступа приложением ненужной.

Связанные операции: streamWrapper::url_stat, stream_context_get_options.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::dir_opendirПодготовить отдельный источник имён каталога по URL.

PHP 4.3.2+ · standard

streamWrapper::dir_opendir(string $path, int $options): bool

Подготовить отдельный источник имён каталога по URL.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
pathURL переданный opendir; разбор адреса не заменяет проверку доступа.
optionsФлаги движка; страница не задаёт отдельный перечислимый публичный набор.
Возвратtrue directory handle готов; false отказ.
СостояниеХранить handle/позицию каталога в экземпляре, затем освободить в dir_closedir.
Каталог как последовательность имён
  1. opendir получает адрес каталога
  2. dir_opendir открывает источник списка
  3. PHP возвращает directory resource
  4. readdir просит по одному имени

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

Каталог — поток имён, а не поток содержимого файлов. opendir создаёт объект wrapper и вызывает dir_opendir; после true вызывающий код получает directory resource. Реализация хранит собственный iterator/handle в экземпляре, отдельно от состояния обычного stream_open.

Открыть каталог и получить список документовСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $directory = opendir('lesson://store/items');
        if ($directory === false) { throw new RuntimeException('Не открыть список'); }
        $names = [];
        try {
            while (($name = readdir($directory)) !== false) {
                if ($name !== '.' && $name !== '..') { $names[] = $name; }
            }
        } finally { closedir($directory); }
        sort($names, SORT_STRING);
        echo 'Документы: ', implode(', ', $names), "\n";
Ожидаемый результат
Документы: a.txt, b.txt

dir_opendir готовит источник имён; dir_readdir получает имена по одному. Файлы не прочитаны только от открытия каталога. Порядок ОС не обещан, поэтому результат сортируется.

Отказать при отсутствии каталогаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        set_error_handler(fn($level) => $level === E_WARNING);
        try { var_dump(opendir('lesson://store/missing')); }
        finally { restore_error_handler(); }
Ожидаемый результат
bool(false)

Внутренний opendir не открыл отсутствующий путь, callback вернул false. Директория не создаётся автоматически: это отдельная операция mkdir.

path — исходный URL, options — переданные движком флаги. Официальная страница не определяет для этого callback самостоятельный пользовательский набор значений options, поэтому не придумывайте режимы вроде «1 = рекурсивно». Отсутствие поддерживаемой directory-операции лучше сообщить отказом; реализация чтения файлов не обязана уметь перечислять каталог.

Связанные операции: streamWrapper::dir_readdir, streamWrapper::dir_closedir, streamWrapper::mkdir.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::dir_readdirВернуть следующее имя каталога либо строго false при завершении обхода.

PHP 4.3.2+ · standard

streamWrapper::dir_readdir(): string|bool

Вернуть следующее имя каталога либо строго false при завершении обхода.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет; используется состояние открытого каталога.
ВозвратСтрока имени, false когда записей больше нет. true тоже прекращает обход, но не рекомендуется.
ПорядокЗависит от источника; сортировка отдельно.
ОшибкиОтсутствие callback E_WARNING; небулевы нестроки исторически приводятся к строке, но на это не стоит опираться.
Обход до строгого false
  1. Directory хранит текущую запись
  2. Callback возвращает её имя
  3. Позиция переходит дальше
  4. false завершает, строка 0 остаётся именем

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

dir_readdir возвращает следующее имя строкой, а когда записи закончились — строго false. Имя "0" является допустимым, поэтому цикл while ($name = readdir(...)) неверен: он остановится на таком файле. Возвращать true вместо конца исторически тоже завершает обход, но использовать следует именно false.

Получить имена файлов без предположения о порядке ОССамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $directory = opendir('lesson://store/items');
        $names = [];
        try {
            while (($name = readdir($directory)) !== false) {
                if ($name !== '.' && $name !== '..') { $names[] = $name; }
            }
        } finally { closedir($directory); }
        sort($names, SORT_STRING);
        echo implode(',', $names), "\n";
Ожидаемый результат
a.txt,b.txt

Мы отделили чтение от сортировки. Порядок файловой системы не является контрактом readdir; сортировка нужна, если пользователю или тесту требуется устойчивый порядок.

Прочитать документ с допустимым именем 0Самостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $directory = opendir('lesson://store/names');
        try {
            while (($name = readdir($directory)) !== false) {
                if ($name === '0') { echo $name, ': ', file_get_contents('lesson://store/names/' . $name); }
            }
        } finally { closedir($directory); }
Ожидаемый результат
0: Заказ без присвоенного номера

Строка0 ложноподобна, но не равна false. Callback возвращает имя, а содержимое читается отдельной операцией. Нестрогое условие потеряло бы этот документ.

Небулевы исторические значения движок приводит к строке, но новый wrapper должен соблюдать string|false. Callback возвращает имя элемента, не его содержимое; чтение выполняется отдельным fopen. Не выдавайте неконтролируемую бесконечную последовательность: состояние обхода должно двигаться вперёд. При отсутствующем методе PHP выдаёт E_WARNING.

Связанные операции: streamWrapper::dir_opendir, streamWrapper::dir_rewinddir, streamWrapper::dir_closedir.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::dir_rewinddirСбросить iterator каталога к началу для следующего readdir.

PHP 4.3.2+ · standard

streamWrapper::dir_rewinddir(): bool

Сбросить iterator каталога к началу для следующего readdir.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
Возврат callbacktrue сброс выполнен, false не удалось.
ГраницаНе snapshot, не блокировка каталога и не перемотка файловых байтов; вызывающая rewinddir имеет собственный контракт возврата.
Повторный обход
  1. Iterator дошёл до части/конца списка
  2. rewinddir вызывает callback
  3. Позиция перечисления сброшена
  4. Следующий readdir снова начинает список

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

rewinddir сбрасывает состояние перечисления так, чтобы следующий readdir снова вернул первый элемент. Это не fseek по байтам: directory iterator может быть списком имён архива, запросом к хранилищу или системным directory handle.

Повторить просмотр того же каталогаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $directory = opendir('lesson://store/items');
        try {
            foreach (['Первый просмотр', 'После rewinddir'] as $label) {
                $names = [];
                while (($name = readdir($directory)) !== false) {
                    if ($name !== '.' && $name !== '..') { $names[] = $name; }
                }
                sort($names, SORT_STRING); echo $label, ': ', implode(', ', $names), "\n";
                rewinddir($directory);
            }
        } finally { closedir($directory); }
Ожидаемый результат
Первый просмотр: a.txt, b.txt
После rewinddir: a.txt, b.txt

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

После предпросмотра прочитать содержимое документовСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $directory = opendir('lesson://store/items');
        try {
            $names = [];
            while (($name = readdir($directory)) !== false) {
                if ($name !== '.' && $name !== '..') { $names[] = $name; }
            }
            sort($names, SORT_STRING); echo 'Предпросмотр: ', implode(', ', $names), "\n";
            rewinddir($directory);
            $documents = [];
            while (($name = readdir($directory)) !== false) {
                if ($name !== '.' && $name !== '..') { $documents[$name] = file_get_contents('lesson://store/items/' . $name); }
            }
            ksort($documents, SORT_STRING);
            foreach ($documents as $name => $text) { echo $name, ': ', $text; }
        } finally { closedir($directory); }
Ожидаемый результат
Предпросмотр: a.txt, b.txt
a.txt: Заказ104: ready
b.txt: Заказ105: packed

Первый проход показывает имена, второй заново перечисляет и читает содержимое. Без rewinddir второй цикл не начал бы работу. Реальная папка может измениться между проходами; повторная проверка чтения нужна в рабочем сервисе.

Связанные операции: streamWrapper::dir_readdir, streamWrapper::dir_opendir.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::dir_closedirОсвободить ресурсы открытого перечисления каталога.

PHP 4.3.2+ · standard

streamWrapper::dir_closedir(): bool

Освободить ресурсы открытого перечисления каталога.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
АргументыНет.
Возврат callbacktrue успешно, false отказ; внешний closedir не является передачей этого boolean бизнес-коду.
ОбязанностьОсвободить всё, выделенное при dir_opendir/обходе.
Уборка каталога
  1. Код закончил или прервал обход
  2. finally вызывает closedir
  3. PHP вызывает dir_closedir объекта
  4. Внутренний iterator закрыт

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

closedir завершает именно directory resource. В callback освободите iterator, файлы архива или блокировки, которые выделили для перечисления. Для обычного файла используется stream_close; смешивание этих handles вызывает ошибки.

Закрыть перечисление, сохранив доступ к документамСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $directory = opendir('lesson://store/items');
        closedir($directory);
        echo is_resource($directory) ? "Список ещё открыт\n" : "Ресурс списка закрыт\n";
        echo 'Документ сохранён: ', file_get_contents('lesson://store/items/a.txt'), "\n";
Ожидаемый результат
Ресурс списка закрыт
Документ сохранён: A

dir_closedir закрывает iterator каталога. Он не удаляет каталог или его файлы; для чтения документа используется свой файловый ресурс.

Освободить каталог после досрочного выходаСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        $directory = opendir('lesson://store/items');
        try {
            while (($name = readdir($directory)) !== false) {
                if ($name === 'a.txt') { echo "Найден a.txt\n"; break; }
            }
        } finally { closedir($directory); }
        var_dump(in_array('dir_close', LessonFileBridge::$events, true));
Ожидаемый результат
Найден a.txt
bool(true)

finally закрывает ресурс и после break, и после исключения. Не нужно дочитывать каталог до конца ради освобождения его handle.

Связанные операции: streamWrapper::dir_opendir, streamWrapper::stream_close.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::mkdirСоздать каталог в хранилище wrapper с учётом режима и запроса рекурсивного создания.

PHP 5+ · standard

streamWrapper::mkdir(string $path, int $mode, int $options): bool

Создать каталог в хранилище wrapper с учётом режима и запроса рекурсивного создания.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
pathАдрес создаваемого каталога.
modeРежим от mkdir; смысл/umask зависят от backend и ОС.
optionsБитовая маска, в частности STREAM_MKDIR_RECURSIVE.
Возвратtrue создан, false отказ; отсутствующий callback вызывает E_WARNING.
ContextПубличное свойство обновляется, если вызывающей операции передан контекст.
Создание иерархии
  1. mkdir передаёт URL, mode и recursive
  2. PHP кодирует recursive в options
  3. Wrapper проверяет адрес/права
  4. Backend создаёт требуемые каталоги

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

mkdir по собственной схеме должен создать каталог именно в её хранилище. mode и recursive — запросы к этой реализации, а не доказательство, что любую файловую систему можно преобразовать в POSIX. Если wrapper вообще не поддерживает создание каталогов, официальный контракт рекомендует не объявлять метод: PHP сможет сообщить отсутствие операции.

Подготовить папку и положить в неё лист выдачиСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

if (!mkdir('lesson://store/new', 0750)) { throw new RuntimeException('Не создать каталог'); }
file_put_contents('lesson://store/new/104.txt', "104:ready\n");
echo file_get_contents('lesson://store/new/104.txt');
Ожидаемый результат
104:ready

mkdir callback получает путь, mode и options. recursive=true превращается в бит STREAM_MKDIR_RECURSIVE; обычный вызов создаёт один уровень. mode0750 относится к модели прав ОС и не устанавливает POSIX-права в Windows.

Подготовить папку и положить в неё лист выдачиСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

if (!mkdir('lesson://store/new/exports', 0750, true)) { throw new RuntimeException('Не создать каталог'); }
file_put_contents('lesson://store/new/exports/104.txt', "104:ready\n");
echo file_get_contents('lesson://store/new/exports/104.txt');
Ожидаемый результат
104:ready

mkdir callback получает путь, mode и options. recursive=true превращается в бит STREAM_MKDIR_RECURSIVE; обычный вызов создаёт один уровень. mode0750 относится к модели прав ОС и не устанавливает POSIX-права в Windows.

Связанные операции: streamWrapper::rmdir, streamWrapper::url_stat, stream_context_get_options.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::renameПереименовать файл или каталог, проверив исходный и целевой адреса.

PHP 5+ · standard

streamWrapper::rename(string $path_from, string $path_to): bool

Переименовать файл или каталог, проверив исходный и целевой адреса.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
path_fromТекущий URL ресурса.
path_toНовый URL; нужно отдельно проверить право записи и допустимость назначения.
Возвратtrue операция выполнена, false отказ.
Ошибки / поддержкаЕсли переименование не поддерживается, не объявлять метод; отсутствие даёт E_WARNING. Context обновляется при его передаче.
Перенос имени
  1. Проверить источник
  2. Проверить назначение и конфликт имён
  3. Backend выполняет поддерживаемый перенос
  4. Успех не равен обещанию атомарности любой системы

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

rename переносит имя файла или каталога внутри возможностей данного wrapper. Обе стороны нужно проверить независимо; безопасный исходный путь не делает безопасным target. Удаление существующего destination, атомарность и поддержка перемещения между хранилищами определяются backend — callback не добавляет эти гарантии автоматически.

Переименовать экспорт и проверить его содержимоеСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        var_dump(rename('lesson://store/tea.txt', 'lesson://store/menu.txt'));
        clearstatcache();
        var_dump(file_exists('lesson://store/tea.txt'));
        echo file_get_contents('lesson://store/menu.txt');
Ожидаемый результат
bool(true)
bool(false)
tea
milk

Изменился адрес, содержимое сохранилось. Fixture destination не существовал; опыт не утверждает одинаковую политику перезаписи во всех ОС.

Переименовать каталог целикомСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        var_dump(rename('lesson://store/items', 'lesson://store/archive'));
        echo file_get_contents('lesson://store/archive/a.txt'), "\n";
        clearstatcache();
        var_dump(is_dir('lesson://store/items'));
Ожидаемый результат
bool(true)
A
bool(false)

Содержимое каталога доступно под новым префиксом. Не копировали вручную все файлы; локальный backend поддерживает перенос имени каталога в пределах этого хранилища.

Связанные операции: streamWrapper::url_stat, streamWrapper::unlink, streamWrapper::mkdir.

Маршрут от первого потока к контекстам и фильтрам.

streamWrapper::rmdirУдалить поддерживаемый каталог, не превращая вызов в неявное рекурсивное удаление.

PHP 5+ · standard

streamWrapper::rmdir(string $path, int $options): bool

Удалить поддерживаемый каталог, не превращая вызов в неявное рекурсивное удаление.

Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.

Часть контрактаЗначение
pathURL каталога для удаления.
optionsФлаги API; упоминание recursive в prototype не делает обычный rmdir рекурсивным.
Возвратtrue удалён, false отказ; неподдерживаемые возможности нельзя изображать успехом.
ПоддержкаЕсли удаления каталогов нет, метод не объявляют; отсутствие E_WARNING. Context передаётся при наличии.
Контролируемое удаление каталога
  1. Проверить точный адрес
  2. Проверить поддерживаемый договор
  3. Пустой каталог удаляется
  4. Непустой остаётся при отказе

streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.

rmdir удаляет каталог по договору хранилища. Обычный PHP rmdir не является рекурсивным удалением дерева. На странице callback упомянут общий options с STREAM_MKDIR_RECURSIVE; это не даёт внешнему rmdir параметр recursive и не требует автоматически удалять дочерние файлы. Наша лаборатория поддерживает только пустой каталог и отвергает такой рекурсивный запрос.

Удалить только созданный пустой каталогСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        mkdir('lesson://store/empty', 0750);
        var_dump(rmdir('lesson://store/empty'));
        clearstatcache();
        var_dump(is_dir('lesson://store/empty'));
Ожидаемый результат
bool(true)
bool(false)

Ресурс создан и удалён внутри изолированной папки опыта. После удаления очищаем stat-кэш для новой проверки.

Сохранить непустой каталог при отказеСамостоятельный пример
<?php
declare(strict_types=1);

require __DIR__ . '/wrapper-lab.php';
lessonRegister();

        set_error_handler(fn($level) => $level === E_WARNING);
        try { var_dump(rmdir('lesson://store/items')); }
        finally { restore_error_handler(); }
        echo file_get_contents('lesson://store/items/a.txt'), "\n";
Ожидаемый результат
bool(false)
A

Внутри items остаются файлы. Отказ не запускает скрытую рекурсию и не удаляет их. Для отдельной операции удаления дерева нужны явный scope, проверки и другой алгоритм.

Связанные операции: streamWrapper::mkdir, streamWrapper::unlink, streamWrapper::url_stat.

Маршрут от первого потока к контекстам и фильтрам.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.