Передовой опыт: используйте поток в PHP
Передавайте данные правильно, используя поток, скрытую жемчужину PHP
Если вы часто путешествуете, вы наверняка проводите в аэропортах больше времени, чем вам хотелось бы.
Я люблю путешествовать, но все это ожидание, потом проверка, потом снова ожидание просто беспокоят меня.
Например, в одной из моих последних поездок мне пришлось ждать несколько часов в пункте назначения, чтобы получить свой багаж обратно.
Минуты и минуты рядом с пустой конвейерной лентой с надеждой, что следующий выплюнутый машиной чемодан будет моим.
Это напомнило мне об одной функции, которая удивительно хорошо работает в PHP.
Потоки!
Если вы когда-либо работали с файлами и архивами с помощью PHP-скриптов, вы использовали потоки.
Они необычны, но не настолько сложны.
Сегодня вы здесь, чтобы познакомиться с ними.
Сериал
Это серия рекомендаций по PHP,
В этой серии мы рассмотрим, какие передовые методы должен соблюдать веб-разработчик при создании кода PHP или управлении им.
Если вы пропустили предыдущие выпуски, перейдите по ссылкам ниже
Дезинфекция, проверка и побег
Безопасность и управление паролями
Обработка ошибок и исключений
Даты и время
Потоки
Что такое потоки
Потоки впервые появились в PHP 4.3.
Но, может быть, из-за своей ниши или просто из-за отсутствия контента в Интернете, они редко известны.
В руководстве по сертификации Zend для PHP 7 Эндрю Бик описывает их как «конвейерную ленту вещей, которые приходят к вам одна за другой».
Хорошая новость заключается в том, что при использовании PHP вам не нужно ждать, пока вещи прибудут, вы можете просто пойти и получить их.
Итак, что такое потоки?
Поток — это передача данных между местами.
Местами может быть файл, ZIP-архив, подключение даже процесса через командную строку.
Потоки PHP имеют функции, которые помогают разработчикам управлять различными ресурсами,
Я уверен, что вы уже видели функции fopen(), fwrite(), fgets() или же file_get_contents()хорошо потоки обеспечивают реализацию, которая работает в фоновом режиме этих функций.
Обертки
Я уверен, что вы понимаете разницу между открытием файла или веб-страницы, у них обоих есть контент, но это два совершенно разных типа потоковых данных, поэтому для них требуются разные протоколы.
Например, мы можем открыть веб-страницу, подключившись к удаленным веб-серверам с помощью HTTP, HTTPS или SSL, или прочитать архив ZIP или TAR.
Эти протоколы называются потоковыми обертками. Они предоставляют уникальный интерфейс, который инкапсулирует все эти различия.
Каждый поток формируется из схемы и цели, вот формат:
схема://цель
Выглядит знакомо?
Если не просто посмотреть на адресную строку вашего браузера.
PHP включает список оболочек, встроенных в язык.
- файл:// Доступ к локальной файловой системе
- http:// Доступ к URL-адресам HTTP(s)
- фтп:// Доступ к URL-адресам FTP
- php:// Доступ к различным потокам ввода/вывода
- zlib:// Потоки сжатия
- данные:// Данные (RFC 2397)
- глобус:// Найти пути, соответствующие шаблону
- фар // Архив PHP
- ssh2:// Безопасная оболочка 2
- рар:// РАР
- огг:// Аудиопотоки
- ожидать:// Потоки взаимодействия процессов
В нашем случае php:// может получить доступ к php://stdin, php://stdout, php://stderr, php://вход, php://выход, php://fd, php://память, php://temp а также php://фильтр.
Пока это все хорошо, но вам может быть интересно, как на самом деле используются эти потоки.
Давайте сделаем пример.
Как вы читаете тело REST API, когда пользователь обновляет некоторые детали где-то в Интернете?
Что запрос PUT и нет $_PUT[variable’] в PHP;
Ответ через поток
$input = file_get_contents('php://input');
parse_str($input, $props);
В другом примере мы можем использовать файловую оболочку и прочитать файл:
$handle = fopen("file:///etc/host", r);
while (feof($handle) !== true) {
echo fgets($handle);
}
fclose($handle);
Распространенное заблуждение состоит в том, что PHP работает как fopen(), fgets(), fclose() предназначены только для файловой системы,
Эти функции отлично работают со всеми оболочками, которые их поддерживают.
Мы можем использовать fopen() на файлы, ZIP-архив и Dropbox (с оберткой Dropbox) и Amazon S3 (с собственной оболочкой).
Создайте свою собственную оболочку
Если ваш скрипт нуждается в особых требованиях, PHP позволяет создать собственную оболочку самостоятельно.
В приведенном ниже фрагменте мы собираемся создать оболочку потока, которая вызывает функцию обратного вызова для чтения:
/ The following class is the wrapper, it has several parameters and the getContext() stream_open(), stream_read() and stream_eof() methods
class CallbackUrl
{
const WRAPPER_NAME = 'callback';
public $context;
private $_cb;
private $_eof = false;
private static $_isRegistered = false;
public static function getContext($cb)
{
if (!self::$_isRegistered) {
stream_wrapper_register(self::WRAPPER_NAME, get_class());
self::$_isRegistered = true;
}
if (!is_callable($cb)) return false;
return stream_context_create(array(self::WRAPPER_NAME => array('cb' => $cb)));
}
public function stream_open($path, $mode, $options, &$opened_path)
{
if (!preg_match('/^r[bt]?$/', $mode) || !$this->context) return false;
$opt = stream_context_get_options($this->context);
if (!is_array($opt[self::WRAPPER_NAME]) ||
!isset($opt[self::WRAPPER_NAME]['cb']) ||
!is_callable($opt[self::WRAPPER_NAME]['cb'])) return false;
$this->_cb = $opt[self::WRAPPER_NAME]['cb'];
return true;
}
public function stream_read($count)
{
if ($this->_eof || !$count) return '';
if (($string = call_user_func($this->_cb, $count)) == '') $this->_eof = true;
return $string;
}
public function stream_eof()
{
return $this->_eof;
}
}
// Here is the class that will contain the text object that need to be instanciated
class Text {
private $_string;
public function __construct($string)
{
$this->$_string = $string;
}
public function read($count) {
return fread($this->$_string, $count);
}
}
// We create a new Text object and open it using our brand-new wrapper CallbackUrl eventually will loop until the file ends
$text = new Text(fopen('/etc/services', 'r'));
$handle = fopen('callback://', 'r', false, CallbackUrl::getContext(array($text, 'read')));
while(($row = fread($handle, 128)) != '') {
print $row;
}
Потоковые фильтры
По мнению многих разработчиков, которые часто используют поток, реальная сила этой функции PHP скрыта за фильтрами.
Потоковые фильтры позволяют фильтровать и преобразовывать данные, которые в данный момент передаются.
Представьте, что вы можете преобразовать и вывести все строки в файле в верхнем регистре, пока вы читаете файл.
Есть 2 способа использования фильтров потока
- Использование stream_filter_append($stream, $filtername, $read_write)
- Использование обертки потока php://filter
stream_filter_append()
Давайте посмотрим на пример, и я прокомментирую его после
$handle = fopen('file.txt', 'rb');
stream_filter_append($handle, 'string.toupper');
while(feof($handle) !== true) {
echo fgets($handle);
}
fclose($handle);
В первой строке
мы открываем файл.txt который должен содержать строку в нижнем регистре, которую мы хотим преобразовать в верхний регистр.
Затем мы присоединяем (добавляем) дескриптор файла к фильтру потока. string.toupper
Теперь PHP знает, что когда мы перебираем халдл, символы должны отображаться в верхнем регистре.
Прямо как по волшебству!
php://фильтр
Иногда вам нужно будет использовать такие функции, как файл() или же fpassthru() которые не дают вам возможности прикрепить фильтры после вызова функций.
Это означает, что вы не можете добавить фильтр потока.
В этом случае вы используете php://фильтр и используйте его прямо при открытии файла.
Фрагмент следует
$handle = fopen('php://filter/read=string.toupper/resource=file.txt', 'rb');
while(feof($handle) !== true) {
echo fgets($handle);
}
fclose($handle);
Снова прикрепляем фильтр string.toupper к текст.txt файл при чтении, но на этот раз мы сделали это по команде открытия;
Обратите внимание на формат, который мы здесь используем: фильтр/чтение= /ресурс= /
Создание пользовательских фильтров потока
Потоковые фильтры — это мощный инструмент, однако встроенный фильтр, предоставляемый PHP, довольно ограничен, чтобы компенсировать это ограничение, PHP позволяет нам создавать собственные потоковые фильтры,
На самом деле пользовательские фильтры являются основной причиной, по которой мы используем фильтры.
Пользовательские потоковые фильтры — это просто классы, которые расширяют поведение php_user_filter()
Это все.
В этом классе должно быть реализовано 3 метода.
Первый метод, который мы рассмотрим чуть ниже, — это фильтр()то имеем onCreate и при закрытии.
Перед тем, как погрузиться и создать поток по нашим собственным разъяснениям.
Потоки PHP делят данные на разделы по несколько байтов каждый, эти разделы обычно называют сегментами.
Каждый потоковый фильтр получает и управляет одним или несколькими из этих сегментов одновременно.
Давайте вместе создадим собственный фильтр потока.
Процесс требует 3 шагов
- Создайте фильтр, реализующий php_user_filter()
- Зарегистрируйте этот новый фильтр
- Используйте новый фильтр в реальном скрипте
Создать фильтр
В этих примерах мы собираемся создать фильтр, который редактирует грязные или спамные слова из текстовых файлов.
Как было сказано ранее, нам нужно создать класс, который должен реализовать php_user_filter чем реализовать фильтр() метод.
class DirtyWordsFilter extends php_user_filter
{
public function filter($in, $out, &$consumed, $closing)
{
$words = ['grime', 'dirt', 'grease'];
$wordData = [];
foreach ($words as $word) {
$replacement = array_fill(0, mb_strlen($word), '*');
$wordData[$word] = implode('', $replacement);
}
$bad = array_keys($wordData);
$good = array_values($wordData);
while ($bucket = stream_bucket_make_writeable($in)) {
$bucket->data = str_replace($bad, $good, $bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
Теперь, когда фильтр готов, нам нужно зарегистрировать его, это довольно просто.
Нам просто нужно использовать функцию stream_filter_register() и передайте имя фильтра, которое идентифицирует наш новый фильтр и имя класса.
stream_filter_register('dirty_words_filter', 'DirtyWordsFilter');
Вот страница из официального руководства PHP
Мы готовы использовать наш фильтр, как мы это делали в приведенных выше примерах:
$handle = fopen('file.txt', 'rb');
stream_filter_append($handle, 'dirty_words_filter');
while(feof($handle) !== true) {
echo fgets($handle);
}
fclose($handle);
Все, мы в безопасности и больше не будем говорить грязных слов.
Потоковые контексты
Последний раздел этой темы касается контекстов,
Рассматривайте контексты потока как оболочку для набора параметров, которые могут изменить поведение потока.
Чтобы создать контекст потока, вам нужно использовать stream_context_create() функция.
Он принимает два параметра, оба являются необязательными ассоциативными массивами.
Давайте используем контекст потока для чего-то странного, например, для отправки HTTP-запроса POST с использованием file_get_contents()
$request = '{"username":"nico"}';
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json;charset=utf-8;\r\n" . "Content-Length: " . mb_strlen($requestBody),
'content' => $requestBody
]
]);
$response = file_get_contents('https://mywebsite.com/users', false, $content);
Контекст потока — это ассоциативный массив, в котором верхний ключ — это имя оболочки потока.
Перейдите к руководству для получения дополнительной информации о контексте потока.
Если вы обнаружили что-то новое, узнать больше так же просто, как нажать на изображение ниже.
Вывод
Иногда путешествие может быть стрессовым, и вам может понадобиться добраться до работы, чтобы немного расслабиться.
Теперь, если вам нужно использовать файлы в вашем скрипте или управлять известными вам архивами,
Потоки PHP помогут вам в этом.
Существует не так много случаев, когда вы можете их использовать, и, на самом деле, их удобство использования довольно скрыто от большинства веб-разработчиков.
Но они действительно просты в использовании, и, на мой взгляд, этот контент может помочь вам открыть для себя эту маленькую жемчужину языка PHP.