PHP: полный мануал с примерами, функциями и ООП
Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Дерево руководства
Глава 109 · От первого потока к своему протоколу
Streams: потоки, контексты, фильтры и свои протоколы
Полная категория Streams: от ресурса в памяти до сетевых состояний, очередей buckets и 25 callbacks пользовательского wrapper.
Сначала результат: экспорт в памяти без отдельного файла
Одна строка, ресурс и его позиция
Сначала сохраняем tea в памяти, затем читаем те же байты. Имя ресурса не является его содержимым.
- 1$stream → пустой канал↓
fopen создаёт php://memory. Позиция 0, байтов ещё нет.
- 2fwrite: t e a |↓
Сохранены три байта. Вертикальная черта показывает позицию после a.
- 3rewind: | t e a↓
Байты остались на месте; позиция вернулась перед t.
- 4$text = tea↓
stream_get_contents прочитал остаток и вернул отдельную строку.
- 5fclose освобождает ресурс
Строка $text остаётся доступна, а читать закрытый $stream уже нельзя.
Позиция измеряется в байтах. Эта модель подходит перематываемому memory-потоку; сеть не обязана поддерживать rewind.
Нужно подготовить маленький экспорт, а библиотека принимает открытый файл. Создавать настоящий файл для трёх байтов необязательно: PHP может открыть поток в памяти. Сначала запишем короткий артикул tea, вернём позицию к началу и прочитаем результат. Это механика одного открытого ресурса; затем применим её к файлу выгрузки ниже. Сначала увидим полезное действие; устройство фильтров, сети и своих протоколов понадобится значительно позже.
- fopen открывает пустой php://memory
- fwrite сохраняет байты tea; позиция после них
- rewind ставит позицию перед t
- stream_get_contents возвращает tea в PHP
- 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 для файла означает путь, для сети — адрес с требованиями конкретного протокола; нельзя трактовать их одинаково.
- Адрес выбирает wrapper: где взять/куда сохранить
- Context задаёт параметры этого обращения
- Filter меняет проходящие байты
- Приложение читает/пишет через общий stream resource
| Слой | Пример / чего не делает |
|---|---|
| Wrapper | file/http/свой lesson: реализует доступ к источнику, не обязательно шифрует. |
| Transport | tcp/udp/tls: механизм соединения под сетевым источником; список отличается от wrappers. |
| Context | http.timeout или ssl.peer_name: политика обращения, не само выполнение запроса. |
| Filter | Base64/сжатие/свой преобразователь: стадия байтов, не открытие другого адреса. |
| PHP/SAPI output buffer | ob_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-лабораторией. Проверяйте возможности, а не название версии само по себе.
<?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 удобен только при осознанной общей политике: его изменение влияет на другой код этого выполнения.
- Массив http.timeout=2 описывает настройку
- stream_context_create создаёт context resource
- Открытие получает этот resource явным аргументом
- 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 — визуально отменить первое.
- Код передаёт tea в fwrite
- Цепь WRITE получает входные байты
- string.toupper выпускает TEA
- Хранилище сохраняет результат
- 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 в этом механизме пропускается.
- Первый блок содержит te: два байта приняты
- Фильтр хранит pending=te и возвращает FEED_ME
- Второй блок содержит a + перевод строки
- Из pending собрана tea, новый bucket содержит TEA
- 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, не меняет прошлые отправленные данные. |
<?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 только ради совместимости.
У объекта публично объявляют $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 и собственный накопитель также должны быть ограничены.
<?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_RDSTREAM_SHUT_WRSTREAM_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_READSTREAM_FILTER_WRITESTREAM_FILTER_ALL | Цепь чтения / записи / обе; ALL = READ | WRITE, при обеих создаются разные экземпляры. stream_filter_append |
STREAM_MUST_SEEKSTREAM_IGNORE_URL | Внутренние, в этом публичном API не используемые флаги: требование seekability (возможна копия), запрет plugin wrappers. Не передавайте произвольно в fopen. stream_get_meta_data |
STREAM_PF_INETSTREAM_PF_INET6STREAM_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_ICMPSTREAM_IPPROTO_IPSTREAM_IPPROTO_RAWSTREAM_IPPROTO_TCPSTREAM_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_INFOSTREAM_NOTIFY_SEVERITY_WARNSTREAM_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_SELECTSTREAM_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_OWNERSTREAM_META_OWNER_NAME | Запрос владельца по целому ID / строковому имени. streamWrapper::stream_metadata |
STREAM_META_GROUPSTREAM_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_ERRORS | Wrapper должен сообщать ошибки открытия; если флаг не установлен, не создавать сообщения. 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_BUFFERSTREAM_OPTION_WRITE_BUFFER | Запрос read/write buffering: mode и размер в callback. streamWrapper::stream_set_option |
STREAM_BUFFER_NONESTREAM_BUFFER_LINESTREAM_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_ONPSFS_FEED_MEPSFS_ERR_FATAL | Статус filter: выход готов / нужны ещё данные / невосстановимый отказ. Это взаимоисключающие результаты, не произвольная OR-маска. php_user_filter::filter |
PSFS_FLAG_NORMALPSFS_FLAG_FLUSH_INCPSFS_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_CLIENT | TLS 1.2, при поддержке криптографической библиотекой. Роль клиента. stream_socket_enable_crypto |
STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT | TLS 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_SERVER | TLS 1.2, при поддержке криптографической библиотекой. Роль сервера. stream_socket_enable_crypto |
STREAM_CRYPTO_METHOD_TLSv1_3_SERVER | TLS 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.
- Лимит меню равен 3 байтам
- Читатель запрашивает не больше 4 байтов
- tea имеет 3 — принимаем
- 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. |
- В context сохранён timeout=2
- get_options возвращает массив
- Приложение читает только безопасное поле
- Запрос выполняет отдельное открытие
Перед запуском учебного импорта полезно проверить именно нужную настройку. 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+. |
- Context содержит GET и timeout=2
- Setter заменяет только timeout на 5
- GET остаётся
- Следующая операция получает обновлённый пакет
Нужно изменить только 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. |
- В context уже GET
- Добавили timeout и follow_location
- В resource теперь три поля
- Сохранение отличается от запроса
Настроек несколько: 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 не исполняет. |
- notification — обработчик событий
- options.http.timeout — настройка HTTP
- get_params возвращает оба слоя
- Приложение читает только нужные поля
Кроме настроек wrapper, контекст может нести callback уведомлений. get_params позволяет посмотреть оба слоя: отдельное поле notification и вложенный массив options. Параметры не нужно искать внутри http.
<?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 одинаковый набор необязательных параметров.
<?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.options | Wrapper → option → value; это вложенный массив настроек. |
| Возврат / ошибки | Современная сигнатура true; неправильные типы/структура приводят к исключениям, не к надёжному false-протоколу. |
- Приложение создало callable
- set_params сохраняет его как notification
- HTTP/FTP wrapper позже сообщает события
- Callback обрабатывает только нужные коды
Обработчик прогресса можно подключить после создания контекста. set_params изменяет общие параметры. Поддерживаемые поля этой страницы — notification и options; произвольное поле business_id не превращается в новый API уведомления.
<?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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| options | null/пропуск — получить существующий default; массив wrapper → option → value дополнительно записывает поля. |
| Возврат | Ресурс общего stream-context, который используют подходящие открытия без явного context. |
| Версии / ошибки | PHP 5.1+; nullable options с 8.0. Неправильная вложенность ValueError, тип TypeError. |
| Граница | Не создаёт независимый clone и не гарантирует, что уже открытый поток заново применит настройки. |
- get_default получает один ресурс
- Переданные options меняют его
- Следующее открытие без явного context видит общую политику
- Отдельный create позволяет избежать этой связи
У процесса есть контекст по умолчанию: его используют открытия без явно переданного context. get_default возвращает именно этот общий ресурс. Если передать options, функция одновременно изменит его — название get не означает безусловно чистое чтение.
<?php
declare(strict_types=1);
$first = stream_context_get_default();
$second = stream_context_get_default();
var_dump($first === $second);
Ожидаемый результат
bool(true)
Две переменные обращаются к одному общему контексту. Контекст, созданный через create, был бы отдельным ресурсом.
<?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; не посылает запрос, не сбрасывает все настройки через []. |
- Главный скрипт задаёт общую политику
- Последующие открытия без context зависят от неё
- Сторонняя библиотека не должна менять её молча
- Точечная операция выбирает отдельный 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 последствия могут затронуть следующую задачу в том же процессе.
<?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. |
| severity | STREAM_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. |
- HTTP wrapper сообщает PROGRESS:128
- PHP вызывает вашу функцию с шестью аргументами
- Она обновляет выбранное состояние
- Основная операция отдельно возвращает данные/отказ
Команда скачивает свой отчёт и хочет показать полученное количество байтов. 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 / CONNECT | RESOLVE описывает разрешение адреса, но поддержка этого уведомления ещё не реализована по официальной странице констант. 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 может сразу резервировать большой буфер. |
- pickup.txt содержит номер и две позиции
- fgets забирает номер104 и LF
- Позиция стоит перед TEA
- 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 не делает их произвольно перематываемыми.
<?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. |
- Открыть pickup.txt для чтения
- Открыть archive.txt для записи
- Перенести17байт между ресурсами
- Закрыть и показать сохранённый 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 проще.
<?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. |
- tea|mint
- Первое чтение возвращает tea и съедает |
- Следующее чтение возвращает mint
- 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. |
- php://memory хранит 104:ready
- mode сообщает w+b
- seekable разрешает попытку перемотки
- После 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. |
- include_path содержит templates
- Имя receipt.txt ищется по правилам открытия
- Получаем абсолютный путь или false
- Исполнение/чтение остаётся отдельным действием
Для собственного шаблона известно короткое имя, а каталог выбирается конфигурацией 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. |
- supports_lock проверяет capability
- flock отдельно пытается захватить lock
- Только после успеха выполняется защищённая работа
- 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 или права. |
- Путь report.txt классифицирован local
- Файл может отсутствовать или быть недоступным
- Проверка прав и допустимости идёт отдельно
- HTTP loopback не превращается в local file
Инструмент выбирает обработку между файловым и URL-wrapper. is_local смотрит категорию wrapper, а не делает DNS, не измеряет расстояние до сервера и не проверяет, что путь безопасен. HTTP на 127.0.0.1 остаётся URL-потоком; data:// тоже не следует считать обычным локальным файлом.
<?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 не разрешает читать произвольный путь пользователя.
<?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. |
- Проверяем конкретный stream
- TTY допускает выбранный интерактивный режим
- Файл получает обычные строки
- Сам 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-сертификата. |
- Сборка зарегистрировала tcp/udp/TLS
- get_transports возвращает имена
- Политика приложения выбирает допустимый transport
- Соединение проверяется отдельно
Прежде чем выбирать transport для сокета, узнайте, зарегистрирован ли он в данной сборке PHP. Transports — tcp, udp, tls и подобные механизмы. Это другой список, чем wrappers вроде http или file. Наличие tls ещё не доказывает доступность сети, доверенных сертификатов или совместимость сервера.
<?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 будет отдельным вызовом.
<?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. |
- file:// — файловые операции
- http:// — HTTP wrapper
- php://temp — временное хранение
- Список регистрации говорит о наличии, не о правах
Код получил путь вида 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. |
- Был исходный размер порции
- Установили 1024 и сохранили прежний
- Read по-прежнему задаёт нужный ему объём
- Владелец восстанавливает политику
При работе с потоком 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. Это не подсчёт прочитанных байтов.
<?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 и не обещает атомарность между процессами. |
- Приложение просит size=0
- Wrapper принимает или отказывает
- Код проверяет status===0
- Только затем полагается на выбранную политику
Если нужно изменить буферизацию чтения, 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. |
- Попросили unbuffered
- Получили статус wrapper
- Проверили допустимость продолжения
- Сам fwrite и его число байтов проверяются отдельно
Программа пишет результат в поток и хочет изменить буферизацию записи. set_write_buffer просит 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 | Открытый поток с поддержкой режима. |
| enable | true — blocking, false — non-blocking. |
| Возврат | true при успехе, false при отказе; не прежнее состояние. |
| Ошибки / платформы | Неверный/закрытый ресурс TypeError. Локальные файлы Windows не поддерживают non-blocking I/O. |
| Граница | Пустая строка не всегда EOF. Нет автоматического event loop, timeout или гарантии полного fwrite. |
- Blocking read ждёт согласно своей политике
- Non-blocking read возвращается сразу
- Приложение сохраняет незавершённую работу
- 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. |
- Connect имеет свой timeout
- После connect setter задаёт ожидание read/write
- Приложение отдельно ограничивает всю задачу
- После read metadata различает timeout и обычный результат
У TCP-соединения уже завершён connect, но удалённая сторона может молчать. set_timeout ограничивает поддерживаемые операции ожидания ввода-вывода этого потока. Это не timeout создания соединения и не общий deadline всего импорта, состоящего из многих операций.
Следующие примеры подключают приложенный 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_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.
<?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 очищаются, не проверяясь. |
- read содержит два ожидаемых потока
- Select ждёт событие или timeout
- Read-array остаётся только с готовыми ресурсами
- 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 используется один раз для наблюдения, не в горячем бесконечном цикле.
<?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
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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| address | transport://target: Internet host:port, IPv6 в [], Unix path при поддержке. |
| error_code / error_message | Выходные переменные по ссылке для ошибки ОС; 0 вместе с false не успех. |
| flags | BIND обязателен для server; LISTEN для TCP-подобных, не для UDP. |
| context | Ресурс настроек или null; nullable с PHP 8.0. |
| Возврат / ошибки | Ресурс listener/datagram socket либо false с warning при отказе. Типы/ресурсы проверяются; stream_socket_accept нужен отдельно для TCP. |
- Server слушает 127.0.0.1:свободный порт
- Client подключается к этому адресу
- Accept создаёт отдельный peer
- Server остаётся для приёма следующих соединений
Для локального учебного сервиса нужен адрес, к которому сможет подключиться клиент. server создаёт слушающий ресурс; входящего клиента он ещё не принимает. В примерах выбираем только loopback 127.0.0.1 и порт 0 — ОС выделит свободный порт, поэтому не занимаем заранее известный порт приложения.
<?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.
<?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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| address | transport://target, для TCP/UDP host:port; IPv6 в [], Unix path при поддержке. |
| error_code / error_message | Выходные переменные по ссылке. false при нулевом коде может означать ошибку до connect. |
| timeout | Только синхронный connect; null — default_socket_timeout. Не timeout всего запроса. |
| flags | CONNECT; дополнительно ASYNC_CONNECT и/или PERSISTENT с отдельными условиями. |
| context | Ресурс контекста или null, nullable с PHP 8.0. |
| Возврат / ошибки | Открытый stream либо false с предупреждением. Успех UDP/open/persistent не гарантирует готовый сервис или успешный бизнес-ответ. |
- Client открывает транспортную связь
- Программа пишет команду
- Серверная сторона читает байты
- Программа проверяет ответ, время и состояние
Сервису нужен клиентский канал к известному серверу. 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. |
- Server держит адрес приёма
- Accept ожидает один входящий connect
- Возвращает отдельный peer resource
- Данные читаются из 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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| socket | Socket stream, не объект Socket другого расширения. |
| remote | true — противоположная сторона, false — локальная сторона ресурса. |
| Возврат | Строка имени/адреса либо false, если получить его нельзя. Формат зависит от транспорта. |
| Граница | Не обратный DNS, не проверка авторизации и не универсальный парсер host/port. Неверный/закрытый ресурс TypeError. |
- Client local — его исходящий порт
- Client remote — порт сервера
- Peer local совпадает с client remote
- 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 — отдельный жизненный цикл. |
- Pair создаёт ресурс 0 и ресурс 1
- Запись в 0 читается из 1
- Ответ из 1 читается из 0
- Каждый владелец закрывает свои ресурсы
Для обмена между двумя участниками иногда не нужен видимый 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.
| Параметр | Документированные семейства значений |
|---|---|
| domain | STREAM_PF_INET, STREAM_PF_INET6, STREAM_PF_UNIX; доступность и поддержка ОС различаются. |
| type | STREAM_SOCK_STREAM, DGRAM, RAW, RDM, SEQPACKET; не каждая комбинация реализована, RAW может требовать специальных прав. |
| protocol | STREAM_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. |
| mode | STREAM_SHUT_RD — дальнейший приём; WR — передача; RDWR — оба направления. |
| Возврат | true при успехе, false при отказе. |
| Границы | Ресурс остаётся для fclose. Буферы могут быть опустошены или нет; не гарантия доставки/бизнес-успеха. Неверный ресурс TypeError; недопустимый mode нужно исключить контрактом приложения. |
- Client отправил report
- SHUT_WR завершил только его запись
- Peer видит EOF и отправляет ACK
- 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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| socket | Socket stream, соединённый или нет. |
| length | Максимум принимаемых байтов; прикладная политика должна задать допустимый положительный предел. Для UDP недостаточный предел способен отбросить хвост сообщения. |
| flags | 0 обычно; STREAM_PEEK — не потреблять, STREAM_OOB — внеполосные данные при поддержке транспорта. Можно сочетать поддерживаемые биты. |
| address | Выходная переменная адреса другой стороны, если получен; не доказательство доверия. |
| Возврат / ошибки | Binary string либо false при отказе; пустые данные интерпретируются по транспорту/протоколу. Неверный ресурс TypeError, системный отказ может предупредить. |
- Отправитель передал abcdef одной datagram
- Receiver выделил только 3 байта
- В Windows-опыте получил false
- Остаток не остался для следующего 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 и считать очередь обработанной.
<?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 | Строка байтов сообщения; кодировка/формат согласуются протоколом. |
| flags | 0 либо поддерживаемый STREAM_OOB. STREAM_PEEK предназначен только для чтения. |
| address | Пустая строка — адрес соединения; альтернатива числовой IPv4:port / [IPv6]:port. |
| Возврат | Число отправленных байтов либо false при отказе; проверяйте полноту и отдельный ответ, если он нужен. |
| Граница | Не доставка UDP, не подтверждение бизнес-операции. Неверный ресурс/типы TypeError; сетевой отказ может предупредить. |
- sendto передал PING локальному транспорту
- Receiver действительно получил datagram
- Он отдельно отправил PONG
- Только прикладной ответ подтверждает выбранный этап
Для 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 на получателе такого подтверждения не давало бы.
<?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. |
| enable | true включить TLS, false выключить по договору протокола; это не команда зашифровать произвольную строку. |
| crypto_method | CLIENT/SERVER маска допустимых методов; null использует ssl.crypto_method context. |
| session_stream | Необязательный совместимый поток для параметров сессии, не гарантия её возобновления; nullable с PHP 8.0. |
| Возврат | true успех; false отказ; 0 продолжить non-blocking согласование. Проверять строго. |
| Отказы / границы | Ошибки транспорта/сертификата могут выдать warning и false; неверные ресурсы/типы TypeError. Crypto без проверки peer identity не обеспечивает нужное доверие. |
- TCP уже создан
- Обе стороны выполняют TLS handshake
- Клиент проверяет сертификат и имя
- Только 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 для любой нагрузки.
<?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; клиентские и серверные роли различаются.
<?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 и согласования. После теста вся временная папка удаляется общим проверяющим инструментом.
<?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. |
| mode | 0 выводит направления из режима открытия; READ/WRITE/ALL задают их явно. |
| params | Любое значение по контракту обработчика, например массив параметров Base64. |
| Возврат | Ресурс экземпляра для stream_filter_remove; false при отказе создания. |
| Порядок / буферы | Добавление в конец; READ-буфер отличается от поведения второй операции, как описано выше. |
- fwrite передаёт вход
- Первый фильтр меняет байты
- Следующие получают его результат
- Поток сохраняет конечные байты
Нужно преобразовать выгрузку при записи, не собирая второй полный файл в переменной. Сначала откройте поток, затем прикрепите обработчик к направлению WRITE. Он меняет байты между fwrite и хранилищем. READ действует между хранилищем и читающим кодом. Обработчик не делает весь файл «изменённым навсегда»: это отдельная стадия конкретного открытого потока.
<?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. |
| mode | 0 выводит направления из режима открытия; READ/WRITE/ALL задают их явно. |
| params | Любое значение по контракту обработчика, например массив параметров Base64. |
| Возврат | Ресурс экземпляра для stream_filter_remove; false при отказе создания. |
| Порядок / буферы | Добавление в начало; READ-буфер отличается от поведения второй операции, как описано выше. |
- fwrite передаёт вход
- Первый фильтр меняет байты
- Следующие получают его результат
- Поток сохраняет конечные байты
Нужно преобразовать выгрузку при записи, не собирая второй полный файл в переменной. Сначала откройте поток, затем прикрепите обработчик к направлению WRITE. Он меняет байты между fwrite и хранилищем. READ действует между хранилищем и читающим кодом. Обработчик не делает весь файл «изменённым навсегда»: это отдельная стадия конкретного открытого потока.
<?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 — разные события. |
- Объявить класс обработки
- Зарегистрировать имя → класс
- Прикрепить экземпляр к потоку
- Читать/писать — 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. |
- Вход больше не поступает этому фильтру
- Накопленный хвост проходит дальше
- Экземпляр отключается
- Поток остаётся открытым
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 остаётся таким.
<?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, параметры/порядок цепочки конкретного потока или гарантию успеха преобразования. |
- PHP загружает расширения
- Приложение регистрирует свои имена
- get_filters возвращает реестр
- Прикрепление проверяет конкретную конфигурацию
Перед использованием необязательного преобразователя проверьте реестр установленной сборки. Список относится к доступным именам, а не к фильтрам уже открытого потока. Некоторые записи — семейства со звёздочкой; 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; это внутренний протокол, а не произвольный массив. |
- Входной bucket извлечён
- Исходные байты учтены
- Данные при необходимости изменены
- 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 меняют относительный порядок. |
| Ошибки | Неверные ресурсы/тип объекта отвергаются; вставка во вход требует защиты от повторной бесконечной обработки. |
- Выход содержит полезный блок
- Создан отдельный префикс
- prepend помещает префикс первым
- Ранее отправленные байты не трогаются
Внутри 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. |
- Входная очередь содержит блоки
- make_writeable извлекает первый
- Код обрабатывает его data
- Следующий вызов берёт следующий либо 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. Ограничивайте собственный буфер. |
- Пришли части логической строки
- Фильтр хранит ограниченный хвост
- new создаёт готовый блок
- 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 | Прибавлять исходные принятые байты; ссылка обязательна, длина выхода может отличаться. |
| closing | true завершающий проход; false обычная обработка. Это не размер пакета и не номер вызова. |
| Возврат | Один из PSFS_PASS_ON, PSFS_FEED_ME, PSFS_ERR_FATAL. |
| Ошибка алгоритма | Забытый bucket теряет данные, неверный consumed ломает учёт, неограниченный pending расходует память. |
- PHP передаёт входную очередь
- Фильтр принимает и учитывает байты
- Готовые блоки переходят в выход
- Статус сообщает: готово / жду / отказ
Внешний код вызывает 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 при прикреплении фильтра; не при одной регистрации имени. |
- append выбирает зарегистрированный класс
- PHP задаёт имя и params
- onCreate проверяет и инициализирует
- 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. |
- filter получает финальный проход
- Остаток данных передаётся в out
- onClose освобождает собственные ресурсы
- Экземпляр завершён
Обработчик может иметь счётчики, временный ресурс или собственный буфер. 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. |
| flags | 0 локальная схема; STREAM_IS_URL схема с URL-ограничениями PHP. |
| Возврат | true зарегистрировано; false не удалось, часто с warning; ошибки типов/класса могут быть исключениями. |
| Срок | Реестр текущего выполнения; открытие создаёт отдельные объекты wrapper. |
- lesson связано с LessonFileBridge
- file_get_contents получает lesson://store/pickup.txt
- PHP создаёт объект и вызывает callbacks
- Читатель получает «Заказ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. |
| flags | 0 локальная схема; STREAM_IS_URL схема с URL-ограничениями PHP. |
| Возврат | true зарегистрировано; false не удалось, часто с warning; ошибки типов/класса могут быть исключениями. |
| Срок | Реестр текущего выполнения; открытие создаёт отдельные объекты wrapper. |
- Alias регистрирует LessonFileBridge под lesson
- URL выбирает зарегистрированный класс
- Методы открывают собственный pickup.txt
- Читатель видит лист выдачи
Нужно дать существующему коду привычный 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 с классом. |
- Схема зарегистрирована
- Открытый поток хранит свой wrapper
- unregister убирает запись схемы
- Живой ресурс работает до 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. |
| Граница | Не восстанавливает произвольный пользовательский класс и не откатывает действия уже открытых потоков. |
- PHP хранит исходный реестр
- unregister меняет активный реестр
- restore берёт встроенную реализацию
- Новые открытия используют её
restore возвращает исходный встроенный обработчик PHP, ранее отменённый или заменённый. Это не стек ваших регистраций: удалённый пользовательский класс PHP для такого восстановления не запоминает. Состояние меняется для всех последующих открытий текущего выполнения.
<?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. |
- Код передаёт URL и режим в fopen
- PHP создаёт объект wrapper
- stream_open проверяет и открывает источник
- 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 предупреждаются и теряются. |
- fread просит часть данных
- PHP при необходимости запрашивает wrapper
- stream_read возвращает не больше count
- 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; завышенный результат предупреждается и обрезается. |
- Вход содержит N байтов
- Хранилище принимает K ≤ N
- Позиция сдвигается на K
- write возвращает K, вызывающий обрабатывает остаток
streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.
write принимает строку байтов и возвращает число действительно записанных. int здесь не произвольный код успеха: true или 1 вместо длины сообщает запись одного байта. При ограниченном источнике разрешена частичная запись; вызывающему коду нужно продолжить с оставшейся части, не дублируя уже записанную.
<?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 | Смещение в байтах, знак зависит от выбранной базы. |
| whence | SEEK_SET / SEEK_CUR / SEEK_END; текущий PHP нормализует CUR в SET. |
| Возврат | true позиция обновлена; false не удалось или метод не реализован. |
| Связь с tell | После успеха PHP уточняет позицию через stream_tell; буферные seek могут не вызывать callback. |
- Выбрать базовую позицию
- Добавить offset и проверить допустимость
- Переместить внутренний источник
- 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-вызовов. |
- Источник имеет собственный offset
- PHP может держать непрочитанный буфер
- tell сообщает offset источника
- Внешний 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. |
- Чтение изменило позицию
- Wrapper узнаёт состояние источника
- EOF true только при завершении
- Перемотка может снова разрешить чтение
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. |
| Граница | Требуется доступ на запись и поддержка источника; файл может потерять хвост без возможности отката. |
- ftruncate запрашивает новую длину
- Wrapper проверяет допустимость
- Хранилище укорачивается или расширяется
- Позиция отдельно от размера
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. |
- register хранит имя класса
- Операция обращается к схеме
- PHP создаёт отдельный объект без аргументов
- Конкретный 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 при обычном закрытии записанного файла, вопреки краткой странице. |
| Назначение | Финальная уборка объекта, не гарантия выполнения бизнес-действия. |
- fclose завершает stream resource
- stream_close освобождает внутренний файл
- Внешняя ссылка может держать объект
- Последняя ссылка исчезает — destruct
streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.
Деструктор относится к времени жизни объекта, а stream_close — к закрытию открытого потока. Обычно эти события идут рядом, но ссылка на wrapper_data может удерживать объект после fclose. Поэтому необходимые файловые ресурсы освобождают в stream_close, а не рассчитывают, что деструктор немедленно завершит всё.
<?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. |
- Приложение вызывает fclose
- PHP завершает необходимые записи
- stream_close освобождает handles
- Внешний 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. |
- write мог положить байты в кэш
- fflush вызывает stream_flush
- Wrapper передаёт кэш своему источнику
- 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 или транзакции.
<?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 при запросе сведений. |
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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| path | URL только с ://, который требуется проверить. |
| flags | Маска STREAM_URL_STAT_LINK и STREAM_URL_STAT_QUIET. |
| Возврат | Полный массив формата stat; false при отсутствии/отказе. |
| Ошибки | QUIET не должен порождать сообщения; без QUIET wrapper сообщает отказ. Отсутствующий метод вызывает E_WARNING. |
- file_exists/is_file/filesize получают URL
- PHP вызывает url_stat с флагами
- Wrapper возвращает mode, size и остальные поля
- 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 может хорошо читаться и одновременно не проходить проверку существования в библиотеке.
<?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_as | STREAM_CAST_FOR_SELECT или STREAM_CAST_AS_STREAM. |
| Возврат | Внутренний stream resource либо false; не int и не внешний ресурс самого wrapper. |
| Граница | Требуется поддержка операции underlying транспортом/ОС; наличие метода не создаёт такую поддержку. |
- select видит пользовательский поток
- PHP просит stream_cast(FOR_SELECT)
- Wrapper возвращает underlying socket
- ОС проверяет его готовность
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 ещё использует.
<?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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| operation | LOCK_SH / LOCK_EX / LOCK_UN; бит LOCK_NB просит не ждать. |
| Возврат | true операция выполнена, false отказ/неподдерживаемая блокировка. |
| Триггеры | flock, file_put_contents с LOCK_EX; освобождение при завершении зависит от владельца underlying ресурса. |
| Ошибки | Отсутствующий метод может дать E_WARNING; платформенные возможности проверяются для выбранного backend. |
- Первый handle получает EX
- Второй EX|NB видит занятость
- Первый освобождает UN
- Второй получает блокировку
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. |
- stream_set_* формирует option и значения
- PHP вызывает stream_set_option
- Wrapper настраивает внутренний ресурс
- Успех не выдумывается при отказе
streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.
Этот callback переводит общую настройку потока в возможности конкретного источника. Не возвращайте true просто потому, что узнали имя option: нижний ресурс должен принять соответствующую настройку. Буфер, таймаут и blocking — разные вещи.
<?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.
<?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. Отключение буфера одного уровня не означает отсутствия любых буферов ОС/протокола.
| option | arg1 / arg2 |
|---|---|
| STREAM_OPTION_BLOCKING | 1 блокировать или 0 не блокировать; arg2 не используется, в 8.5.11 null. |
| STREAM_OPTION_READ_TIMEOUT | Секунды / микросекунды таймаута чтения. |
| STREAM_OPTION_READ_BUFFER | STREAM_BUFFER_NONE или STREAM_BUFFER_FULL / требуемый размер. |
| STREAM_OPTION_WRITE_BUFFER | STREAM_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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| path | URL вида scheme://… для изменения. |
| option | STREAM_META_TOUCH/OWNER_NAME/OWNER/GROUP_NAME/GROUP/ACCESS. |
| value | Массив времён, имя, числовой ID или mode в зависимости от option. |
| Возврат | true изменение выполнено; false ошибка либо операция не реализована. |
| Платформа | Некоторые операции недоступны на данной ОС/FS или с текущими правами. |
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 или под любым пользователем.
| option | value |
|---|---|
| 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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| path | URL переданный opendir; разбор адреса не заменяет проверку доступа. |
| options | Флаги движка; страница не задаёт отдельный перечислимый публичный набор. |
| Возврат | true directory handle готов; false отказ. |
| Состояние | Хранить handle/позицию каталога в экземпляре, затем освободить в dir_closedir. |
- opendir получает адрес каталога
- dir_opendir открывает источник списка
- PHP возвращает directory resource
- 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; небулевы нестроки исторически приводятся к строке, но на это не стоит опираться. |
- Directory хранит текущую запись
- Callback возвращает её имя
- Позиция переходит дальше
- 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; сортировка нужна, если пользователю или тесту требуется устойчивый порядок.
<?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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| Аргументы | Нет. |
| Возврат callback | true сброс выполнен, false не удалось. |
| Граница | Не snapshot, не блокировка каталога и не перемотка файловых байтов; вызывающая rewinddir имеет собственный контракт возврата. |
- Iterator дошёл до части/конца списка
- rewinddir вызывает callback
- Позиция перечисления сброшена
- Следующий 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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| Аргументы | Нет. |
| Возврат callback | true успешно, false отказ; внешний closedir не является передачей этого boolean бизнес-коду. |
| Обязанность | Освободить всё, выделенное при dir_opendir/обходе. |
- Код закончил или прервал обход
- finally вызывает closedir
- PHP вызывает dir_closedir объекта
- Внутренний 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 | Публичное свойство обновляется, если вызывающей операции передан контекст. |
- mkdir передаёт URL, mode и recursive
- PHP кодирует recursive в options
- Wrapper проверяет адрес/права
- 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 обновляется при его передаче. |
- Проверить источник
- Проверить назначение и конфликт имён
- Backend выполняет поддерживаемый перенос
- Успех не равен обещанию атомарности любой системы
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 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| path | URL каталога для удаления. |
| options | Флаги API; упоминание recursive в prototype не делает обычный rmdir рекурсивным. |
| Возврат | true удалён, false отказ; неподдерживаемые возможности нельзя изображать успехом. |
| Поддержка | Если удаления каталогов нет, метод не объявляют; отсутствие E_WARNING. Context передаётся при наличии. |
- Проверить точный адрес
- Проверить поддерживаемый договор
- Пустой каталог удаляется
- Непустой остаётся при отказе
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.
streamWrapper::unlinkУдалить файл по адресу в пределах возможностей и полномочий wrapper.
PHP 5+ · standard
streamWrapper::unlink(string $path): boolУдалить файл по адресу в пределах возможностей и полномочий wrapper.
Поток — открытый канал байтов, например файл или соединение. Переменная со знаком $ хранит ссылку на ресурс; это ещё не строка его содержимого. Скобки после имени функции запускают вызов, аргументы внутри передают настройки, = сохраняет результат. false означает отказ там, где это предусмотрено контрактом; пустая строка и 0 могут быть успешными результатами. Первый опыт чтения потока объясняет открытие, позицию и закрытие.
| Часть контракта | Значение |
|---|---|
| path | URL удаляемого файла, проверяемый реализацией. |
| Возврат | true удалён; false отказ/отсутствие. |
| Поддержка | Если удаления нет, метод не объявляют; отсутствующий callback даёт E_WARNING. |
| Context / граница | Контекст обновляется при передаче; unlink не закрывает любые ранее открытые handles автоматически. |
- Приложение выбирает точный URL
- Wrapper проверяет право/границы
- Backend удаляет имя файла
- false требует различить отсутствие и иной отказ
streamWrapper — название протокола методов в документации, не существующий базовый класс. Эти методы пишет наш LessonFileBridge; PHP вызывает их через обычные файловые функции. Полный wrapper-lab.php приложен к каждому примеру. Он делегирует операции файлам исключительно временной папки этого опыта. Это прозрачный учебный мост, не защита произвольного пути загрузки: в настоящем сервисе нужны полномочия, политика путей, контроль ссылок и гонок.
unlink удаляет имя файла по URL. Это операция по адресу, а не закрытие открытого handle; для закрытия используют fclose/stream_close. На некоторых ОС файл с открытым handle ведёт себя иначе, поэтому пример сначала завершает запись и только потом удаляет свой временный экспорт.
<?php
declare(strict_types=1);
require __DIR__ . '/wrapper-lab.php';
lessonRegister();
$url = 'lesson://store/export.tmp';
file_put_contents($url, "104:ready\n");
echo 'Прочитано: ', file_get_contents($url);
if (!unlink($url)) { throw new RuntimeException('Не удалить промежуточный файл'); }
clearstatcache();
echo file_exists($url) ? "Копия осталась\n" : "Промежуточная копия удалена\n";
Ожидаемый результат
Прочитано: 104:ready
Промежуточная копия удалена
Удаляется только точное имя, которое создали в этом опыте. unlink вызывает URL-callback и не равен fclose или перемещению в корзину.
<?php
declare(strict_types=1);
require __DIR__ . '/wrapper-lab.php';
lessonRegister();
unlink('lesson://store/tea.txt');
set_error_handler(fn($level) => $level === E_WARNING);
try { var_dump(unlink('lesson://store/tea.txt')); }
finally { restore_error_handler(); }
var_dump(file_exists('lesson://store/items/a.txt'));
Ожидаемый результат
bool(false)
bool(true)
Второй запрос не удалил ничего и вернул false; соседний файл остался. Если прикладной API хочет считать «уже отсутствует» успехом, это нужно определить поверх низкоуровневого callback с корректным различением ошибок.
Связанные операции: streamWrapper::stream_close, streamWrapper::rmdir, streamWrapper::url_stat.
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


