Перейти к содержимому
Поиск
Php Разработка

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

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

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 35 · Практика

Паузы, лимиты API и повторные попытки

sleep, usleep и time_nanosleep в реальных интеграциях: когда ждать, когда отложить, когда остановиться.

Пауза нужна текущему процессу: что делает sleep

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

Что уже пригодится. циклы, HTTP-клиент

Последовательное выполнение
  1. Отправить допустимый запрос
  2. Решить, нужна ли пауза
  3. sleep: процесс ждёт
  4. Следующая строка программы

sleep(1) — ждать примерно секунду; usleep(100000) — примерно 100 миллисекунд. Это не таймер браузера и не запуск параллельной работы.

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

echo "Перед паузой\n";
$seconds = 0;
sleep($seconds);
echo "После паузы\n";
Ожидаемый результат
Перед паузой
После паузы

В скачиваемом тесте задано 0 секунд, чтобы он выполнялся мгновенно. Для собственного локального опыта замените 0 на 1: порядок текста сохранится, появится ожидание. Это демонстрация синтаксиса, а не готовый ограничитель частоты.

Следующая ступень. После этого рассматриваем Retry-After, повторные попытки и несколько работников. Долгое ожидание в запросе веб-страницы занимает рабочий процесс; для таких задач нужна очередь.

Почему пауза иногда необходима

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

429: повторяем чтение только в пределах бюджета

Чтение каталога получило Retry-After: 2. Нужно соблюсти паузу, сохранив предел попыток и общего ожидания.

  1. Попытка чтения №1

    Отправляем разрешённый запрос каталога и учитываем эту попытку в общем бюджете.

  2. Ответ 429

    Провайдер ограничил частоту. Разбираем Retry-After по его формату, а не повторяем немедленно.

  3. Остались время и попытки?

    Пауза 2 секунды должна помещаться в бюджет. Иначе прекращаем цикл или откладываем задание.

  4. Выдержать паузу

    Короткое ожидание занимает worker. Длительную задержку лучше хранить как время будущего запуска.

  5. Следующая попытка

    Возвращаемся к чтению с увеличенным счётчиком. Успех завершает цикл; бюджет не обнуляется.

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

Поставщик разрешает два запроса в секунду. Импорт из тысячи товаров без ограничения частоты быстро получает 429 и может быть заблокирован. Пауза между запросами помогает одному последовательному CLI-скрипту соблюдать простой темп. sleep принимает секунды, usleep — микросекунды, time_nanosleep — секунды и наносекунды. Более мелкая единица не обещает такую же точность планировщика: фактическая пауза может быть длиннее.

Сон приостанавливает текущий поток выполнения. Пока HTTP-обработчик спит, PHP-FPM-воркер занят; при удерживаемой сессии или транзакции заняты также блокировки. Поэтому длительную рассылку или импорт не запускают прямо из формы. Форма создаёт задание и показывает его состояние, а CLI-воркер выполняет работу. Для длинной задержки лучше записать available_at и освободить воркер, чем держать его спящим час.

Пауза каждого из десяти процессов по 500 мс не ограничит их суммарный поток двумя запросами в секунду. Общая квота требует координации: очередь с нужной параллельностью либо атомарный распределённый лимитер. Лимиты бывают по аккаунту, токену, endpoint и IP; читайте контракт провайдера. Не выдумывайте универсальную частоту для всех API.

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

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

function fetchWithRetry(callable $send, callable $wait): string {
    $spent = 0;
    for ($attempt=0; $attempt<3; $attempt++) {
        $response = $send();
        if ($response['status'] === 200) return $response['body'];
        if (!in_array($response['status'],[429,503],true) || $attempt === 2) {
            throw new RuntimeException('retry-stopped');
        }
        $seconds = $response['retryAfter'] ?? (2 ** $attempt);
        if (!is_int($seconds) || $seconds < 0 || $seconds > 10 || $spent+$seconds > 10) {
            throw new RuntimeException('retry-budget');
        }
        $wait($seconds);
        $spent += $seconds;
    }
    throw new LogicException('Unreachable');
}
$responses = [['status'=>429,'retryAfter'=>2],['status'=>503],['status'=>200,'body'=>'ready']];
$send = static function () use (&$responses): array { return array_shift($responses); };
$wait = static function (int $seconds): void { echo "wait:$seconds
"; };
echo fetchWithRetry($send,$wait)."
";
Ожидаемый результат
wait:2
wait:2
ready

Тестовый wait только пишет решение, поэтому пример не тормозит запуск. В CLI можно передать обёртку sleep; в очереди — планирование задания. В реальном клиенте общий дедлайн включает время сетевых запросов, а не только сумму пауз.

Retry-After, jitter и неопределённый результат

Retry-After бывает количеством секунд или HTTP-датой. Число нужно проверять как неотрицательное целое с разумным пределом; дату разбирают по HTTP-формату и сравнивают с текущим временем UTC. Если сервер требует ждать дольше допустимого бюджета задачи, её откладывают, а не уменьшают паузу и немедленно снова нагружают сервис. При отсутствии заголовка обычно выбирают экспоненциальную задержку с верхним пределом.

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

Безопасность повтора зависит от операции. GET обычно можно повторять, если он действительно только читает. POST создания платежа после таймаута имеет неизвестный результат: сервер мог выполнить операцию, а ответ потерялся. Для него нужен ключ идемпотентности и протокол получения состояния. Не повторяйте 400/401/403 как временный сбой, если документация явно не говорит обратного.

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

function delaySeconds(int $attempt, ?int $retryAfter, int $jitter): int {
    if ($attempt < 0 || $attempt > 6 || $jitter < 0 || $jitter > 2) {
        throw new InvalidArgumentException('invalid-policy-input');
    }
    if ($retryAfter !== null) {
        if ($retryAfter < 0 || $retryAfter > 60) throw new DomainException('defer-job');
        return $retryAfter + $jitter;
    }
    return min(30, 2 ** $attempt) + $jitter;
}
echo delaySeconds(0,null,1)."
";
echo delaySeconds(2,10,2)."
";
try { delaySeconds(2,3600,0); } catch (DomainException $e) { echo $e->getMessage()."
"; }
Ожидаемый результат
2
12
defer-job

Предел 60 секунд относится к минимальному Retry-After; итог с jitter может достигать 62 секунд. Политика явно откладывает часовой отказ. В рабочем коде jitter получают random_int(0,2), а здесь передают фиксированно.

Тонкости функций ожидания

sleep возвращает 0 при полном ожидании; при прерывании семантика возвращаемого значения зависит от платформы. usleep не возвращает полезного результата, и большие интервалы удобнее выражать sleep или планировщиком. time_nanosleep может вернуть true, false либо массив оставшегося времени при прерывании сигналом. Надёжный воркер должен уметь корректно завершаться по сигналу, а не превращать любое прерывание в бесконечный сон.

Для измерения длительности используйте монотонный hrtime, а для даты следующего задания — календарное время. Системные часы могут корректироваться, поэтому вычисление длительности как time_end-time_start иногда ведёт себя неожиданно. Не используйте busy-wait с пустым циклом: он тратит CPU, хотя никакой полезной работы не происходит.

Медленное место здесь намеренное: ожидание защищает внешний сервис. Оптимизация не означает убрать sleep и получить больше 429. Полезные показатели — число успешных операций, задержка очереди, доля повторов и соблюдение квоты. Если одному импорту мешают другие, нужна координация общего лимита, а не случайное увеличение паузы в каждой копии скрипта.

Общий лимитер и честное состояние задания

Token bucket позволяет небольшие всплески в пределах ёмкости, а leaky bucket задаёт более ровный темп. Выбор зависит от правил провайдера: средняя скорость не всегда заменяет жёсткий лимит каждого окна. При нескольких воркерах изменение счётчика и выдача разрешения должны быть атомарными. Обычные get/set в Redis без согласованного алгоритма оставляют гонку так же, как SELECT перед INSERT.

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

Разделяйте лимит скорости и ограничение параллельности. Десять медленных запросов могут укладываться в секундную квоту, но занять все соединения. Маленькая очередь и ограниченный пул защищают память и воркеры. Метрики успешных операций, возраста очереди и доли 429 помогают понять, где узкое место: в квоте партнёра или в собственной обработке.

Практика: проверьте решение, а не только успешный путь

Нужно отправить 20 000 уведомлений с лимитом 5/с. Почему sleep(1) внутри веб-запроса после каждого пятого сообщения плохой проект?

Разбор решения

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

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

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

random_intВыбрать непредсказуемое целое без самодельного масштабирования и смещения вероятностей.

PHP 7.0+ · random

random_int(int $min, int $max): int

Выбрать непредсказуемое целое без самодельного масштабирования и смещения вероятностей.

Часть вызоваДоговор
min / maxНижняя/верхняя границы PHP int, обе включены; min≤max.
ВозвратРавномерно выбранный int из [min,max]. Не обещание уникальности при повторных вызовах.
ОшибкиValueError при max<min. Отказ CSPRNG — Random\RandomException с PHP 8.2, до этого Exception; неверный тип в strict_types — TypeError.
Диапазон → число → проверка
  1. Границы 5 и 15 включены
  2. PHP выбирает один из 11 вариантов
  3. Проверяем диапазон; при [8,8] получается 8

Два аргумента задают включённые границы: random_int(5, 15) может вернуть и 5, и 15. Знак $ обозначает переменную с результатом; сравнения проверяют, что её значение подходит договору. Здесь нет seed: системную криптографическую случайность не настраивают временем компьютера.

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

$quantity = random_int(5, 15);
echo $quantity >= 5 && $quantity <= 15 ? "От 5 до 15 включительно\n" : "За пределами\n";
echo random_int(8, 8), "\n";
Ожидаемый результат
От 5 до 15 включительно
8

Первый результат меняется, поэтому expected фиксирует его полезный договор. Равные границы допустимы и дают единственное число, но такой диапазон не содержит непредсказуемости.

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

function verificationCode(): string
{
    return sprintf('%06d', random_int(0, 999999));
}
$code = verificationCode();
echo strlen($code), '/', ctype_digit($code) ? 'digits' : 'other', "\n";
Ожидаемый результат
6/digits

sprintf дополняет число до шести цифр, сохраняя варианты вроде 000007. Миллион вариантов — ограниченный поиск: реальная проверка требует срока и ограничения попыток. Это создание строки, а не готовая система входа; код никуда не отправляется.

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

try { random_int(15, 5); }
catch (ValueError) { echo "Нижняя граница больше верхней\n"; }
Ожидаемый результат
Нижняя граница больше верхней

Не вычисляйте random_bytes()%range вручную: остаток обычно даёт смещение вероятностей. Функция сама выбирает равномерное число в заданном замкнутом диапазоне.

Поддерживается диапазон PHP int, включая отрицательные значения; он зависит от разрядности сборки. Для длительных секретов обычно удобнее random_bytes с достаточным пространством вариантов. Разные вызовы могут дать одинаковое число: непредсказуемость и отсутствие повторов — разные требования. Системные источники и старые версии описаны в общей таблице.

Продолжить: random_bytes, Random\Randomizer::getInt.

Как выбрать случайность и прочитать первый вызов.

Обе границы включены. Возвращает int; неверный диапазон даёт ValueError, сбой источника случайности — исключение.

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

$jitter=random_int(0,250);
echo $jitter>=0 && $jitter<=250 ? "within milliseconds budget\n" : "invalid\n";
Ожидаемый результат
within milliseconds budget

Тест проверяет контракт диапазона, не конкретное случайное значение.

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

try { random_int(10,1); } catch (ValueError) { echo "invalid range\n"; }
Ожидаемый результат
invalid range

Ошибку конфигурации обнаруживают до запуска партии фоновых заданий.

Рядом по задаче. random_bytes создаёт токены; mt_rand допустим для несекретных задач, но не паролей и CSRF.

sleepЗаблокировать текущий процесс на целое число секунд.

PHP 4+ · standard

sleep(int $seconds): int

Стоимость: ожидание. Текущий процесс занят паузой. Длительные повторы выносите из HTTP-запроса в управляемую фоновую задачу.

Заблокировать текущий процесс на целое число секунд.

Скобки после имени запускают вызов; значения внутри — аргументы. Знак = сохраняет возвращённый результат в переменную со знаком $. Функция может вернуть число, строку, массив, true/false либо завершить текущий сценарий. Сначала смотрите минимальный вызов и ожидаемый результат, затем переходите к условиям, функциям и обработке ошибок. Как устроен вызов, параметр и return.

Что произойдёт при вызове

Короткая CLI-пауза
  1. Рабочая операция завершилась
  2. Определена ограниченная пауза
  3. sleep блокирует этот процесс
  4. После возврата выполняется следующая строка

Первый запуск и развитие задачи

Пауза не является глобальным rate limiter: несколько воркеров спят независимо. Не держите во время долгого сна HTTP-запрос, сессию или транзакцию; отложите задачу в очередь.

Полный договор вызова

Часть вызоваПолный договор
secondsЦелое число ≥ 0. Дробь вроде 0.25 не задаёт четверть секунды.
Возврат 0Запрошенное ожидание завершилось полностью.
ПрерываниеНа Unix возвращаются оставшиеся секунды; на Windows при прерывании возвращается 192.
Отрицательное значениеValueError с PHP 8; раньше предупреждение и false.

Ошибки и границы. Сигнал может прервать ожидание. Клиентский disconnect обнаруживается только на границе вывода; sleep сам по себе не проверяет соединение.

Версии и платформа. Длительность может быть больше запрошенной из-за планировщика. Поведение прерывания различается по ОС.

Рядом: usleep, time_nanosleep, time_sleep_until.

Карта полного раздела Miscellaneous.

Возвращает 0 при полном ожидании. При прерывании результат зависит от платформы; отрицательное значение вызывает ValueError. Процесс занят всё время ожидания.

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

function importBatches(array $products, callable $wait): void
{
    $batches = array_chunk($products, 2);
    foreach ($batches as $index => $batch) {
        // Здесь рабочий импорт отправил бы пакет и проверил результат.
        echo 'Обработан пакет ', $index + 1, ': ', count($batch), " товара\n";
        if ($index < count($batches) - 1) {
            $wait(2);
        }
    }
}
$fakeWait = static function (int $seconds): void {
    echo 'Пауза ', $seconds, " с\n";
};
importBatches(['TEA', 'COFFEE', 'HONEY', 'MILK'], $fakeWait);

// Эта альтернатива показана для рабочего CLI; в примере не вызывается.
$realWait = static function (int $seconds): void {
    if (PHP_SAPI !== 'cli' || $seconds < 0 || $seconds > 10) {
        throw new InvalidArgumentException('Нужна ограниченная CLI-пауза');
    }
    if (sleep($seconds) !== 0) {
        throw new RuntimeException('Ожидание прервано');
    }
};
Ожидаемый результат
Обработан пакет 1: 2 товара
Пауза 2 с
Обработан пакет 2: 2 товара

Поставщик разрешает небольшие пакеты с промежутком. Подставной wait показывает точное место ожидания без задержки теста; после последнего пакета ждать незачем. В рабочем CLI передают $realWait вместо $fakeWait. Здесь нет реальной отправки и глобального rate limiter: учитывайте Retry-After, фактические квоты и других воркеров. Длинное ожидание переносите в очередь, не держите сессию или транзакцию.

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

try { sleep(-1); } catch (ValueError) { echo "invalid delay\n"; }
Ожидаемый результат
invalid delay

Пример не ждёт. Такую конфигурацию лучше отвергнуть до запуска импорта.

Рядом по задаче. usleep для долей секунды, time_nanosleep для явного остатка при прерывании, ограниченный retry.

usleepЗаблокировать текущий процесс на заданное число микросекунд.

PHP 4+ · standard

usleep(int $microseconds): void

Стоимость: ожидание. Текущий процесс занят паузой. Длительные повторы выносите из HTTP-запроса в управляемую фоновую задачу.

Заблокировать текущий процесс на заданное число микросекунд.

Скобки после имени запускают вызов; значения внутри — аргументы. Знак = сохраняет возвращённый результат в переменную со знаком $. Функция может вернуть число, строку, массив, true/false либо завершить текущий сценарий. Сначала смотрите минимальный вызов и ожидаемый результат, затем переходите к условиям, функциям и обработке ошибок. Как устроен вызов, параметр и return.

Что произойдёт при вызове

Короткая задержка
  1. Рассчитать целое число микросекунд
  2. Проверить допустимый предел
  3. usleep блокирует текущий процесс
  4. Выполнение продолжается после паузы

Первый запуск и развитие задачи

Для целых секунд читаемее sleep. В unit-тесте политику ожидания передавайте callback/объект и записывайте запрос без настоящего сна.

Полный договор вызова

Часть вызоваПолный договор
microsecondsМикросекунда — миллионная доля секунды: 1 000 мкс = 1 мс. На некоторых ОС значения больше 1 000 000 не поддерживаются.
ВозвратПолезного значения нет (void).
Фактическое времяМожет оказаться больше из-за нагрузки, вызова функции и гранулярности таймера.

Ошибки и границы. Не передавайте float: сначала сформулируйте единицу и округление. Пауза не координирует несколько процессов.

Версии и платформа. Граница >1 секунды зависит от ОС; Manual рекомендует sleep для такого интервала.

Рядом: sleep, time_nanosleep, hrtime.

Карта полного раздела Miscellaneous.

Принимает неотрицательное число микросекунд и не возвращает полезное значение. Реальное время может быть больше из-за планировщика; отрицательное значение недопустимо.

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

function waitJitterMilliseconds(int $milliseconds): void {
    if ($milliseconds<0 || $milliseconds>500) throw new InvalidArgumentException('jitter');
    usleep($milliseconds*1000);
}

Определение без ожидания. В рабочем CLI значение можно получить random_int(0,250); это дополнение к backoff, а не общий лимитер.

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

try { usleep(-1); } catch (ValueError) { echo "invalid microseconds\n"; }
Ожидаемый результат
invalid microseconds

Никакой реальной паузы в тесте нет.

Рядом по задаче. sleep проще для секунд; fake wait ускоряет тесты политики.

time_nanosleepЗаблокировать текущий процесс на заданные секунды и наносекунды.

PHP 5+ · standard

time_nanosleep(int $seconds, int $nanoseconds): array|bool

Стоимость: ожидание. Текущий процесс занят паузой. Длительные повторы выносите из HTTP-запроса в управляемую фоновую задачу.

Заблокировать текущий процесс на заданные секунды и наносекунды.

Скобки после имени запускают вызов; значения внутри — аргументы. Знак = сохраняет возвращённый результат в переменную со знаком $. Функция может вернуть число, строку, массив, true/false либо завершить текущий сценарий. Сначала смотрите минимальный вызов и ожидаемый результат, затем переходите к условиям, функциям и обработке ошибок. Как устроен вызов, параметр и return.

Что произойдёт при вызове

Точная единица ожидания
  1. Миллисекунды разделить на секунды и остаток
  2. Проверить обе границы
  3. time_nanosleep ждёт
  4. true либо остаток после сигнала

Первый запуск и развитие задачи

На Windows фактический сон может быть длиннее из-за аппаратной/системной гранулярности. Не путайте requested delay с измеренной длительностью; для проверки измеряйте hrtime.

Полный договор вызова

Часть вызоваПолный договор
secondsНеотрицательное целое число.
nanosecondsНеотрицательное целое строго меньше 1 000 000 000.
true / falsetrue — пауза завершена; false — отказ вызова.
Массив остаткаПри прерывании: seconds и nanoseconds, оставшиеся до конца. Политика повтора принадлежит приложению.

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

Версии и платформа. Сигналы и доступность высокоточного ожидания зависят от платформы.

Рядом: sleep, usleep, hrtime.

Карта полного раздела Miscellaneous.

При полном ожидании возвращает true; при неуспехе false, а при прерывании сигналом может вернуть массив seconds/nanoseconds оставшегося времени. Наносекунды должны быть от 0 до 999999999.

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

function nanosleepHalfSecond(): void {
    $result=time_nanosleep(0,500_000_000);
    if ($result===false) throw new RuntimeException('Wait failed');
    if (is_array($result)) throw new RuntimeException('Wait interrupted; let worker decide');
}

Функция не вызывается; реальный воркер решает, продолжать ли остаток после сигнала.

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

function waitMilliseconds(int $milliseconds): array|bool {
    if ($milliseconds<0 || $milliseconds>2000) throw new InvalidArgumentException('delay');
    return time_nanosleep(intdiv($milliseconds,1000),($milliseconds%1000)*1_000_000);
}

Определение без сна. Отдельные секунды предотвращают неверное значение nanoseconds ≥ 1 000 000 000.

Рядом по задаче. sleep/usleep проще для обычных пауз; очередь лучше для длительного отложения.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

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

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.