Глава 100 · С нуля → полный справочник
URI и URL: полный справочник двух парсеров
От первого имени узла до всех методов RFC3986 и WHATWG, immutable-изменений, нормализации, сериализации и29 типов диагностики. PHP8.5+.
Получить полезную часть ссылки: первый запуск
Выбрать правила адреса до его разбора
Ссылка может быть относительной, содержать кодирование и по-разному пониматься браузерным и RFC-парсером.
- Исходная строка
Адрес ещё не проверен и не означает сетевое соединение.
- Выбранный стандарт
Rfc3986 Uri либо WhatWg Url разбирает строку по своим правилам.
- Компоненты и изменения
Читаем схему, host и путь; сохраняем возвращённый новый объект после изменения.
- Политика приложения
Отдельно проверяем разрешённую цель, схему и правила фактического HTTP-клиента.
Корректный адрес не подтверждает доступность и допустимость ресурса. Нормализация и raw-компоненты решают разные задачи.
На странице магазина есть ссылка https://shop.example/catalog/drill?color=blue#delivery. Нужно взять имя узла, изменить фильтр и сохранить якорь. Если разрезать строку вручную по /, ?, @ и :, легко спутать пароль с host или данные параметра с разделителем. Расширение URI хранит разобранный адрес в объекте и даёт отдельные операции над его частями. Результат первого примера — только имя shop.example; подключений не будет.
- Строка https://shop.example/catalog/drill
- Парсер выделяет scheme=https и host=shop.example
- $address хранит разобранный бланк
- getAsciiHost() возвращает shop.example
Бланк — аналогия структуры, не буквальное устройство памяти и не справка о владельце сайта. Адрес идентифицирует ресурс, но сам по себе не подтверждает его существование. RFC 3986 описывает общую структуру URI; WHATWG URL Standard задаёт веб-алгоритмы, близкие браузерам. Мы сначала используем WHATWG Url для HTTPS, затем сравним правила.
Основа записи: переменная и присваивание, что такое объект и вызов метода. Здесь необходимые символы разобраны до запуска; углубление в классы не требуется для первой строки.
| Запись | Что делает именно здесь |
|---|---|
| <?php | Начинает PHP-файл. Сохраните его как hello-uri.php в UTF-8; запускайте своим PHP 8.5+ через php hello-uri.php. |
| declare(strict_types=1); | Включает строгие скалярные типы вызовов из этого файла; неправильное число вместо ожидаемой строки не следует считать корректным адресом. |
| $address = …; | $address — имя переменной, = сохраняет результат справа, ; завершает команду. |
| new Uri\WhatWg\Url('https://shop.example/catalog/drill') | new создаёт объект; полное имя с \ выбирает класс. Круглые скобки передают строку входа конструктору. Кавычки обозначают текст, а не имя переменной. |
| $address->getAsciiHost() | -> вызывает операцию объекта. Пустые () означают, что дополнительных аргументов нет; результат — строка имени узла или null. |
| echo …, "\n"; | Печатает результат и перевод строки. В терминале ожидается строка ниже примера. |
<?php
declare(strict_types=1);
$address = new Uri\WhatWg\Url('https://shop.example/catalog/drill');
echo $address->getAsciiHost(), "\n";
Ожидаемый результат
shop.example
Не загружены ни каталог, ни картинка; URL разобран в памяти. Для реального HTTP существуют отдельные клиенты. .example — учебное имя, никаких прав или доступности сервера пример не предполагает.
Классы добавлены в PHP 8.5.0. В PHP 8.2–8.4 запись такого new не работает. В PHP 8.5 URI встроено: не нужно придумывать extension=uri для отдельной DLL. Проверьте выбранный исполняемый файл командой php -v и наличие класса; CLI и веб-сервер могут использовать разные PHP. Код проверки ниже честно рассчитан на окружение этого учебника PHP 8.5.11.
<?php
declare(strict_types=1);
echo PHP_VERSION_ID >= 80500 ? "Версия подходит\n" : "Нужен PHP 8.5+\n";
echo class_exists(Uri\WhatWg\Url::class) ? "WHATWG Url доступен\n" : "Класс отсутствует\n";
Ожидаемый результат
Версия подходит
WHATWG Url доступен
::class даёт полное имя класса строкой, class_exists проверяет доступность. У старого PHP ожидаются ветки отказа, а не напечатанный здесь успешный результат. Это проверка окружения, не установка.
Scheme, host, path, query и fragment
Разберём конкретную ссылку по частям. Порядок разделителей задаёт смысл символов: query начинается после первого структурного ?, fragment — после #. Если эти символы являются данными, их кодируют внутри соответствующего компонента.
- https://shop.example:8443
- /catalog/drill
- ?color=blue&page=2
- #delivery
| Компонент | Значение и назначение |
|---|---|
| scheme | https — схема; без :// в getter. |
| host | shop.example — имя узла. Это не обязательно домен: возможны IPv4, IPv6 и другие URI-схемы. |
| port | 8443 — явно заданный порт. Host и port — разные поля. |
| path | /catalog/drill — маршрут ресурса; не локальный файловый путь. |
| query | color=blue&page=2 — строка запроса. Разделение на ключи и значения задаёт приложение, не общая грамматика URI. |
| fragment | delivery — клиентский указатель на часть ресурса. |
| userinfo / credentials | Например anna:demo@ перед host. В учебных стендах можно разобрать; в обычном интерфейсе такие секреты лучше не принимать в URL. |
Authority — часть после // до пути: может включать userinfo@host:port. Нельзя доверять тексту до @ как домену. null означает, что компонента нет; пустая строка может означать, что разделитель был, но значение пустое. getPath возвращает строку, даже если она пустая. RFC и WHATWG не во всех случаях одинаково сохраняют эту разницу.
<?php
declare(strict_types=1);
$missing = new Uri\Rfc3986\Uri('https://shop.example/p');
$empty = new Uri\Rfc3986\Uri('https://shop.example/p?');
echo $missing->getQuery() === null ? "Нет query\n" : "Есть query\n";
echo $empty->getQuery() === '' ? "Пустой query\n" : "Другое значение\n";
Ожидаемый результат
Нет query
Пустой query
=== требует совпадения значения и типа. Простое echo для null и пустой строки вывело бы одинаковую пустоту, поэтому используем условный выбор сообщения: условие ? при-истине : иначе.
RFC 3986: общий URI и две формы компонентов
Uri\Rfc3986\Uri нужен, когда договор системы опирается на RFC 3986: URI может быть абсолютным, относительным, HTTP-ссылкой или идентификатором другой схемы. Это общий синтаксический инструмент, не валидатор доступности веб-сайта. Сохраняются исходные компоненты, а обычные getters и toString показывают нормализованную форму.
- HTTPS://Shop.Example/a/../%7ecard
- Raw: сохранить запись компонентов
- Normalized: lowercase + ~ + устранить a/..
- https://shop.example/~card
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Класс final readonly: нельзя наследоваться и менять готовый объект присваиванием свойств. Публичных изменяемых полей scheme/host/path нет. Получите компонент getter-методом или новый объект через with-метод. clone создаёт отдельный объект с тем же адресом; equals сравнивает адреса по режиму, === проверяет тождество объектов. __toString не предоставлен — выводите toString()/toRawString().
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri;
$u = new Uri('HTTPS://Shop.Example/a/../%7ecard');
echo $u->toRawString(), "\n";
echo $u->toString(), "\n";
Ожидаемый результат
HTTPS://Shop.Example/a/../%7ecard
https://shop.example/~card
use вводит короткое имя Uri в текущем файле; он не загружает пакет Composer и не запускает запрос. Raw не является гарантией байтовой копии: IPv6 сериализуется заново, а разрешение базы уже создаёт новый адрес.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri;
$relative = new Uri('../images/drill.png');
echo $relative->getScheme() === null ? "Относительная ссылка\n" : "Есть схема\n";
$base = new Uri('https://shop.example/catalog/page');
echo $base->resolve($relative->toRawString())->toString(), "\n";
Ожидаемый результат
Относительная ссылка
https://shop.example/images/drill.png
Сам относительный объект корректен. Разрешение требует абсолютной базы. Не используйте относительную базу даже с абсолютным первым аргументом: PHP 8.5.11 отвергает такую комбинацию.
Начать API: Uri\Rfc3986\Uri::__construct, Uri\Rfc3986\Uri::parse, Uri\Rfc3986\Uri::getRawPath, Uri\Rfc3986\Uri::withQuery.
WHATWG: веб-ссылки, IDNA и исправляемый ввод
Uri\WhatWg\Url подходит для ссылок, которые должны разбираться по веб-алгоритму. Он обрабатывает международные домены, специальные схемы и допускает некоторые исправляемые нарушения исходной записи. Относительной строке нужна база: без неё Url::parse возвращает null, а new бросает InvalidUrlException. Полученный объект всегда имеет схему.
- HTTPS://пример.рф:443/a/../p
- IDNA-домен + удалить стандартный порт
- Свернуть сегменты пути
- https://xn--e1afmkfd.xn--p1ai/p
| Правило | Что проверить в приложении |
|---|---|
| Special schemes | ftp, file, http, https, ws, wss. Стандартные порты: ftp21, http/ws80, https/wss443; file без такого порта. У прочих схем другой разбор host/path. |
| Hierarchical и opaque path | https://host/a имеет иерархический путь. mailto:sales@host имеет opaque path: обычный resolve/withPath не превращает его в каталог. |
| Нормализация | Убираются dot-segments, default port и некоторые лишние разделители; Unicode-домен преобразуется IDNA, IPv4 может иметь нестандартную исходную запись. Не считайте входные байты сохранёнными. |
| Диагностика | Мягкие ошибки могут сопровождать успешный объект; они доступны выходным массивом. При жёстком отказе есть exception->errors либо null+errors у parse. |
| with-операции | Следуют URL setter-алгоритмам: могут нормализовать, не применить изменение либо использовать только часть строки. Это не строгая проверка всей строки отдельного поля. |
<?php
declare(strict_types=1);
use Uri\WhatWg\Url;
$u = new Url('HTTPS://пример.рф:443/a/../p');
echo $u->toAsciiString(), "\n";
echo $u->toUnicodeString(), "\n";
Ожидаемый результат
https://xn--e1afmkfd.xn--p1ai/p
https://пример.рф/p
Две формы отражают один объект; Unicode касается отображения домена, а не декодирования всех % в пути. DNS и HTTP не выполняются.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url;
$mail = new Url('mailto:sales@shop.example');
echo $mail->getPath(), "\n";
echo $mail->withPath('/help')->toAsciiString(), "\n";
try { $mail->resolve('help'); } catch (Uri\WhatWg\InvalidUrlException $e) { echo "Эта база не каталог\n"; }
Ожидаемый результат
sales@shop.example
mailto:sales@shop.example
Эта база не каталог
withPath у opaque URL не применяет новый hierarchical путь. Новая цель другой природы требует нового объекта. Это поведение парсера, не отправка почты.
Начать API: Uri\WhatWg\Url::parse, Uri\WhatWg\Url::getAsciiHost, Uri\WhatWg\Url::withHost, Uri\WhatWg\UrlValidationError.
Почему результат with нужно сохранить
Оба класса моделируют адрес и не меняют исходный объект в with-операции. Это удобно для независимых фильтров каталога: базовая ссылка одна, а ссылки «страница2» и «страница3» не перезаписывают друг друга. Переменная может получить новый объект; readonly относится к объекту, а не запрещает присваивать переменной.
- $base: /catalog
- withQuery(page=2) → новый $page2
- withQuery(page=3) → новый $page3
- $base по-прежнему без query
<?php
declare(strict_types=1);
use Uri\WhatWg\Url;
$base = new Url('https://shop.example/catalog');
$page2 = $base->withQuery('page=2');
$page3 = $base->withQuery('page=3');
echo $base->toAsciiString(), "\n";
echo $page2->toAsciiString(), "\n";
echo $page3->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/catalog
https://shop.example/catalog?page=2
https://shop.example/catalog?page=3
Каждый результат явно сохранён. Если нужна только текущая версия, допустимо $base = $base->withQuery(...); это замена ссылки в переменной, а не изменение старого объекта.
<?php
declare(strict_types=1);
$a = new Uri\Rfc3986\Uri('https://shop.example/p');
$b = clone $a;
echo $a === $b ? "Один объект\n" : "Два объекта\n";
echo $a->equals($b) ? "Эквивалентные URI\n" : "Разные URI\n";
Ожидаемый результат
Два объекта
Эквивалентные URI
clone копирует текущий адрес. Для изменения компонента обычно достаточно with: он сам создаёт новый объект. Нельзя считать === сравнением URI-значений.
Эквивалентность и UriComparisonMode
Для списка загружаемых ресурсов две ссылки, отличающиеся #delivery и #payment, часто нужно считать одним адресом. Для списка мест чтения это разные записи. UriComparisonMode заставляет явно назвать выбранное правило: IncludeFragment учитывает fragment; ExcludeFragment не учитывает и используется по умолчанию.
- help#delivery и help#payment
- equals по умолчанию → без fragment
- true для адреса ресурса
- IncludeFragment → false для места чтения
| Pure enum PHP 8.5 | Договор |
|---|---|
| Uri\UriComparisonMode::IncludeFragment | Учитывать fragment; отличается от простого true в аргументе. |
| Uri\UriComparisonMode::ExcludeFragment | Не учитывать fragment; default у equals обоих классов. |
| cases(), name | cases() возвращает оба case; ->name даёт имя. Нет backed value и автоматических from/tryFrom. |
<?php
declare(strict_types=1);
$mode = Uri\UriComparisonMode::IncludeFragment;
$a = new Uri\WhatWg\Url('https://shop.example/help#delivery');
$b = new Uri\WhatWg\Url('https://shop.example/help#payment');
echo $mode->name, "\n";
echo $a->equals($b, $mode) ? "Одна закладка\n" : "Разные закладки\n";
Ожидаемый результат
IncludeFragment
Разные закладки
Enum-case — отдельное значение, его не заменяет строка "IncludeFragment". :: обращается к case по имени класса.
<?php
declare(strict_types=1);
$a = new Uri\WhatWg\Url('https://shop.example/p?a=1&b=2');
$b = new Uri\WhatWg\Url('https://shop.example/p?b=2&a=1');
echo $a->equals($b) ? "эквивалентны\n" : "различаются\n";
Ожидаемый результат
различаются
Одинаковый набор пар не означает одинаковый протокол: порядок/повторы могут влиять на подпись или сервер. equals не знает правил вашего приложения и не сортирует query.
Байты, Unicode и границы компонентов
Процентная запись %20 означает кодированный байт пробела. Она не равна HTML-экранированию & и не является универсальной «безопасной строкой». Сначала задаётся структура адреса, затем кодируются данные каждого компонента. Unicode-домен обрабатывается IDNA; Unicode в сегменте пути обычно представлен percent-encoded UTF-8-байтами.
- q = drill & bit
- http_build_query → q=drill%20%26%20bit
- withQuery → URL
- HTML display → htmlspecialchars отдельно
<?php
declare(strict_types=1);
$input = 'drill & bit';
$query = http_build_query(['q' => $input], '', '&', PHP_QUERY_RFC3986);
$u = (new Uri\WhatWg\Url('https://shop.example/find'))->withQuery($query);
echo $u->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/find?q=drill%20%26%20bit
[] создаёт массив, => связывает ключ q со значением $input. http_build_query кодирует именно данные параметра. Полученное %26 не становится структурным разделителем query.
<?php
declare(strict_types=1);
$a = new Uri\Rfc3986\Uri('https://shop.example/items/a%2Fb');
$b = new Uri\Rfc3986\Uri('https://shop.example/items/a/b');
echo $a->getPath(), "\n";
echo $a->equals($b) ? "Одинаково\n" : "Разная структура\n";
Ожидаемый результат
/items/a%2Fb
Разная структура
RFC-нормализация не раскрывает reserved / из %2F. Rawurldecode всего URL разрушил бы границы; декодируйте только данные компонента согласно протоколу и ровно один нужный раз.
parse_str не является нейтральным преобразованием любого query: он следует PHP-формату, меняет некоторые имена ключей и может терять повторные скалярные ключи. URL encoding, IDNA, UTF-8 и HTML encoding решают разные задачи. Для строгой подписи храните исходный текст и применяйте только предусмотренную протоколом канонизацию.
Исключение, Error и диагностическая запись
Импортёр ссылок должен отличать неправильный адрес от ошибки программы. Исключения прерывают обычный ход до подходящего catch. Объект UrlValidationError лишь описывает событие; его наличие в массиве не означает, что выполнение было прервано.
- parse → объект или null
- errors → список записей
- constructor/with → при отказе exception
- catch читает тип и принимает решение
| Тип | Наследование и назначение |
|---|---|
| Uri\UriException | extends Exception; базовая ветка исключений URI. |
| Uri\InvalidUriException | extends Uri\UriException; не удалось разобрать/создать допустимый RFC URI. |
| Uri\WhatWg\InvalidUrlException | extends Uri\InvalidUriException; отказ WHATWG и public readonly array $errors. catch(InvalidUriException) охватывает обе ветки. |
| Uri\UriError | extends Error; внутренняя ошибка URI. catch(Exception) не охватывает Error; общий предок обеих веток — Throwable. |
| Uri\WhatWg\UrlValidationError | final readonly, не Throwable; одна диагностическая запись context/type/failure. Не бросайте её через throw. |
Первые три базовых типа UriException, InvalidUriException, UriError наследуют конструктор (string $message = "", int $code = 0, ?Throwable $previous = null). Не добавляйте им вымышленных полей. getMessage возвращает строку причины; getCode — код, выбранный источником, а не обязательный HTTP-статус. getPrevious даёт предыдущую причину или null. getFile/getLine указывают место возникновения, getTrace возвращает массив кадров, getTraceAsString — текст, __toString — текстовое представление с диагностикой. Их вывод может содержать пути и аргументы. Клонирование исключения запрещено; создавайте обёртку с previous.
<?php
declare(strict_types=1);
try {
new Uri\WhatWg\Url('https://shop.example:70000/');
} catch (Uri\InvalidUriException $e) {
echo $e instanceof Uri\WhatWg\InvalidUrlException ? "Отказ WHATWG\n" : "Отказ RFC\n";
echo "Проверьте адрес\n";
}
Ожидаемый результат
Отказ WHATWG
Проверьте адрес
Дочернее исключение подходит родительскому catch. Пользователю показана наша безопасная фраза; техническая трассировка и context не публикуются.
<?php
declare(strict_types=1);
$cause = new Uri\InvalidUriException('Некорректная ссылка');
$jobError = new Uri\UriException('Не импортирована карточка 17', 17, $cause);
echo $jobError->getMessage(), "\n";
echo $jobError->getCode(), "\n";
echo $jobError->getPrevious() === $cause ? "Причина сохранена\n" : "Причина потеряна\n";
echo (new Uri\UriError('Тестовый внутренний отказ')) instanceof Error ? "Ветка Error\n" : "Другая ветка\n";
Ожидаемый результат
Не импортирована карточка 17
17
Причина сохранена
Ветка Error
Объекты здесь создаём сами; внутренний сбой парсера не имитируется сетевой аварией. Код17 — локальный номер карточки, не статус протокола.
В PHP 8.5.11 сериализационные hooks имеют отдельные Exception/previous Error границы; они описаны в карточках __unserialize. Не переносите обещания конструктора на любой magic method. Все типы этой ветки появились в PHP 8.5.0.
Как читать soft и hard ошибки WHATWG
Редактор ссылки может показать предупреждение и всё же получить исправленный URL. У каждой записи context — часть входа в месте обнаружения проблемы, type — один из29 enum-case, failure — true при отказе парсинга, false при диагностируемом исправлении. context не является номером позиции и не гарантирует безопасное содержимое для журнала.
- https:shop.example
- WHATWG восстанавливает //
- объект https://shop.example/
- type + failure=false сохраняют факт исправления
<?php
declare(strict_types=1);
$diagnostics = [];
$url = Uri\WhatWg\Url::parse('https:shop.example', null, $diagnostics);
echo $url?->toAsciiString(), "\n";
foreach ($diagnostics as $item) {
echo $item->type->name, ':', $item->failure ? 'отказ' : 'исправление', "\n";
}
Ожидаемый результат
https://shop.example/
SpecialSchemeMissingFollowingSolidus:исправление
?-> вызывает метод лишь у ненулевого объекта. foreach проходит по списку; ->type возвращает enum, ->name — имя его case. Код не выводит context.
<?php
declare(strict_types=1);
$errors = [];
Uri\WhatWg\Url::parse('https:shop.example', null, $errors);
echo count($errors), "\n";
Uri\WhatWg\Url::parse('https://shop.example/', null, $errors);
echo count($errors), "\n";
Ожидаемый результат
1
0
Выходной массив относится только к последней попытке, не накапливает историю. Если нужна история импорта, сохраняйте собственную запись с номером строки до следующего parse.
Все29 типов UrlValidationErrorType
Enum задаёт закрытый набор причин. Это pure enum: есть name и cases(), нет backed value, from() или tryFrom(). Для внешнего формата выберите собственное стабильное отображение имён и версию схемы. Не присваивайте каждому case навсегда одну тяжесть: признак failure находится в конкретной записи парсера.
- http://0x7f.1/
- Канонический host 127.0.0.1
- Ipv4NonDecimalPart
- failure=false: принято с исправлением
| Case UrlValidationErrorType | Условие и практический смысл |
|---|---|
| DomainToAscii | Домен не прошёл преобразование IDNA в ASCII; например, повреждённая xn-- метка. Это проверка имени, не DNS. |
| DomainToUnicode | Ошибка IDNA-преобразования доменного имени в Unicode. Наличие enum-case не обещает, что всякий parser path выдаст его; учитывайте используемую операцию. |
| DomainInvalidCodePoint | После обработки домен special-URL содержит запрещённый символ, например пробел или %00. Нельзя исправлять простым удалением символа. |
| HostInvalidCodePoint | Запрещённый символ в opaque host у non-special схемы. Это другой алгоритм, чем IDNA-домен HTTPS. |
| Ipv4EmptyPart | IPv4 заканчивается точкой, например 192.0.2.1.; парсер может убрать её и сообщить мягкую диагностику. |
| Ipv4TooManyParts | Больше четырёх частей IPv4, например 1.2.3.4.5. Уточнение PHP 8.5.11/WHATWG: меньше четырёх частей не обязательно ошибка, 127.1 допустимо и разворачивается. |
| Ipv4NonNumericPart | Часть кандидата IPv4 не удалось разобрать как число; например, буквенная часть при окончании адреса числом. |
| Ipv4NonDecimalPart | IPv4 использует hex или octal, например 0x7f.1. Это может быть мягкое исправление к 127.0.0.1; не проверяйте IP только по исходному тексту. |
| Ipv4OutOfRangePart | Числовая часть превышает обычный октет 255. Непоследние части должны помещаться в октет; последняя при укороченной записи может занимать оставшиеся октеты. Проверяйте failure конкретного сообщения, а не объявляйте любой случай одинаковым. |
| Ipv6Unclosed | В IPv6-host нет закрывающей ], например https://[::1/. Скобки относятся к синтаксису URL-host. |
| Ipv6InvalidCompression | Начало IPv6 некорректно обозначает сжатие нулей, например [:1]. Одиночный начальный : не заменяет ::. |
| Ipv6TooManyPieces | Более восьми шестнадцатеричных групп в IPv6. Сжатие не разрешает превысить итоговую длину адреса. |
| Ipv6MultipleCompression | Сжатие :: использовано дважды, например [1::2::3]; нельзя однозначно восстановить длины обеих последовательностей нулей. |
| Ipv6InvalidCodePoint | В IPv6 встретился неожиданный символ/окончание вместо hex-цифры либо разделителя, например [::g]. |
| Ipv6TooFewPieces | Без :: указано меньше восьми групп, например [1:2:3]; недостающие группы автоматически не дописываются. |
| Ipv4InIpv6TooManyPieces | В IPv6 с IPv4-хвостом до хвоста уже слишком много групп: IPv4 занимает две из восьми групп, для префикса остаётся не более шести. |
| Ipv4InIpv6InvalidCodePoint | Некорректная часть IPv4-хвоста: пустота, нецифра, ведущий ноль или лишняя часть. У этого хвоста более строгая запись, чем у самостоятельного WHATWG IPv4. |
| Ipv4InIpv6OutOfRangePart | В IPv4-хвосте IPv6 один из четырёх десятичных октетов превышает255, например [::ffff:192.0.2.999]. |
| Ipv4InIpv6TooFewParts | В IPv4-хвосте IPv6 меньше четырёх частей, например [::ffff:192.0.2]. Здесь сокращение до127.1 не применяется. |
| InvalidUrlUnit | Символ или %-последовательность не соответствует URL unit; например пробел в пути. Возможны автоматическое кодирование и мягкая диагностика, это не всегда отказ. |
| SpecialSchemeMissingFollowingSolidus | После special-схемы нет ожидаемой пары //, например https:shop.example. WHATWG может восстановить разделитель. |
| MissingSchemeNonRelativeUrl | Схему не удалось определить, а подходящей базы для относительной ссылки нет. shop.example без https:// — не готовый абсолютный URL; opaque-база также ограничивает разрешение. |
| InvalidReverseSoldius | Обратная косая черта \ используется в special-URL как будто это /. Имя enum-case именно InvalidReverseSoldius; не исправляйте его написание в PHP-коде. |
| InvalidCredentials | Исходный URL содержит credentials. Это может быть мягкая диагностика принятого user:password@host; отдельная политика приложения вправе такое запрещать. |
| HostMissing | Special-URL требует host, но его нет, например https://. file имеет собственные правила пустого host, не обобщайте правило HTTPS. |
| PortOutOfRange | Число порта превышает65535, например :70000. Это относится к WHATWG, а не общему RFC URI. |
| PortInvalid | Порт записан не допустимыми десятичными цифрами, например :abc. Пустой порт — отдельная допустимая ситуация, а не abc. |
| FileInvalidWindowsDriveLetter | Относительная file-ссылка начинается с Windows drive letter при file-базе. Парсер применяет file-правила и может выдать мягкую диагностику. |
| FileInvalidWindowsDriveLetterHost | В file-URL буква диска оказалась в позиции host, например file://C:/docs. Она переносится согласно алгоритму file, но исходная запись диагностируется. |
<?php
declare(strict_types=1);
$errors = [];
$u = Uri\WhatWg\Url::parse('http://0x7f.1/', null, $errors);
echo $u?->getAsciiHost(), "\n";
echo $errors[0]->type === Uri\WhatWg\UrlValidationErrorType::Ipv4NonDecimalPart ? "Не десятичная запись\n" : "Другая запись\n";
echo $errors[0]->failure ? "Отказ\n" : "Мягкая диагностика\n";
Ожидаемый результат
127.0.0.1
Не десятичная запись
Мягкая диагностика
Никакого подключения к localhost нет: обрабатывается строка. При исходящих HTTP-запросах запрет частных/локальных IP должен учитывать конечный разрешённый адрес и перенаправления, а не только этот пример.
<?php
declare(strict_types=1);
$errors = [];
Uri\WhatWg\Url::parse('https://shop.example:70000/', null, $errors);
$type = $errors[0]->type;
$message = match ($type) {
Uri\WhatWg\UrlValidationErrorType::PortOutOfRange => 'Порт должен быть не больше 65535',
Uri\WhatWg\UrlValidationErrorType::HostMissing => 'Укажите имя узла',
default => 'Проверьте адрес',
};
echo $message, "\n";
echo count(Uri\WhatWg\UrlValidationErrorType::cases()), " типов в PHP 8.5\n";
Ожидаемый результат
Порт должен быть не больше 65535
29 типов в PHP 8.5
match сравнивает case по значению и типу. default оставляет общее сообщение для остальных причин. Не копируем низкоуровневую диагностику в HTML; экранирование и политика интерфейса остаются отдельным слоем.
Практика: добавьте ветку PortInvalid с фразой «Порт записывается десятичными цифрами» и проверьте строку https://shop.example:abc/. Ожидаемая политика — отказ, сообщение PortInvalid; остальные case продолжат работать через default.
От синтаксиса к правилам конкретного приложения
Предположим, кнопка может вести только на справку нашего магазина. Нужны не только синтаксически корректный URL, но и явные условия: https, ровно shop.example, обычный порт, без credentials. Ни один парсер сам не знает этих требований. Пример ниже только принимает или отклоняет строки и ничего не загружает.
- Строка кандидата
- Url::parse → объект/null
- schema + ASCII host + port + credentials
- Вернуть bool; перехода ещё нет
Перед функцией разберём новые элементы: function объявляет именованную операцию; string $input — её строковый параметр; : bool обещает true/false. return возвращает ответ вызывающему выражению. && требует выполнения всех условий; null — отсутствие значения. Синтаксис функции подробнее: параметры и return.
<?php
declare(strict_types=1);
function isStoreLink(string $input): bool {
$errors = [];
$url = Uri\WhatWg\Url::parse($input, null, $errors);
return $url !== null
&& $errors === []
&& $url->getScheme() === 'https'
&& $url->getAsciiHost() === 'shop.example'
&& $url->getPort() === null
&& $url->getUsername() === null
&& $url->getPassword() === null;
}
echo isStoreLink('https://shop.example/help') ? "Принято\n" : "Отклонено\n";
echo isStoreLink('https://shop.example@other.example/help') ? "Принято\n" : "Отклонено\n";
echo isStoreLink('https:shop.example') ? "Принято\n" : "Отклонено\n";
Ожидаемый результат
Принято
Отклонено
Отклонено
Первый вызов передаёт строку в $input, parse создаёт $url, return отдаёт true исходному выражению echo. Второй host — other.example, третий имеет мягкую диагностику; наша строгая политика отклоняет оба. Это лишь правила входной ссылки, не полная SSRF-защита HTTP-клиента.
При исходящем HTTP дополнительно контролируют DNS-результат/конечный IP, перенаправления на каждом шаге, допустимые схемы/порты самого клиента, таймауты и размеры ответа. Между проверкой и использованием не должно быть другого парсера с иным прочтением. Нельзя передавать непроверенный исходный ввод после проверки нормализованной копии. Для HTML и журналов также нужны отдельные правила вывода и скрытия секретов.
Практика: функция ссылок пагинации
Соберите ссылки пагинации каталога, сохранив одну доверенную базу. Требования: страница положительная; query сформирован из значений, а не склеен с сырым вводом; базовый объект не изменяется. Сначала предскажите две итоговые строки, затем запустите предложенное решение.
- catalogPage($base, 2)
- Параметры: $base, $page=2
- http_build_query(page=2) → withQuery
- return новый Url → toAsciiString в echo
<?php
declare(strict_types=1);
use Uri\WhatWg\Url;
function catalogPage(Url $base, int $page): Url {
if ($page < 1) {
throw new InvalidArgumentException('Номер страницы должен быть положительным');
}
$query = http_build_query(['page' => $page], '', '&', PHP_QUERY_RFC3986);
return $base->withQuery($query);
}
$base = new Url('https://shop.example/catalog');
echo catalogPage($base, 2)->toAsciiString(), "\n";
echo catalogPage($base, 3)->toAsciiString(), "\n";
echo $base->toAsciiString(), "\n";
try { catalogPage($base, 0); }
catch (InvalidArgumentException $e) { echo "Номер страницы отклонён\n"; }
Ожидаемый результат
https://shop.example/catalog?page=2
https://shop.example/catalog?page=3
https://shop.example/catalog
Номер страницы отклонён
Url $base требует готовый объект, int $page — целое число. if проверяет условие, throw создаёт отказ приложения до сборки URL, catch обрабатывает его. Функция заменяет всю query; для сохранения старых параметров нужен отдельный договор о слиянии, не скрытая потеря фильтров.
Развитие упражнения: передайте категорию отдельным параметром и добавьте её в массив http_build_query перед page. Для tools и2 ожидается https://shop.example/catalog?category=tools&page=2. Не разрешайте заменять host через этот параметр. Для перехода из относительной ссылки используйте resolve и проверьте конечный host; для нужного component — соответствующий with.
Полный справочник методов URI
Все методы этой главы доступны с PHP 8.5.0. Выбирайте RFC и WHATWG по договору источника и следующего потребителя адреса. Между ними нет универсального «лучший для любого URI»; parse_url — прежний разбор компонентов, но не drop-in реализация этих стандартов. Полный справочник ниже включает getter, raw getter, with-операции, сравнение, сериализацию и диагностические конструкторы.
| Задача | Начальные карточки |
|---|---|
| Разобрать или отклонить | Uri\Rfc3986\Uri::parse / Uri\WhatWg\Url::parse |
| Относительная ссылка | Uri\Rfc3986\Uri::resolve / Uri\WhatWg\Url::resolve |
| Домен/кодировка | Uri\Rfc3986\Uri::getHost / Uri\WhatWg\Url::getAsciiHost / Uri\WhatWg\Url::getUnicodeHost |
| Обновить параметры | Uri\Rfc3986\Uri::withQuery / Uri\WhatWg\Url::withQuery |
| Сравнить адреса | Uri\Rfc3986\Uri::equals / Uri\WhatWg\Url::equals |
| Разобрать отказ | Uri\WhatWg\InvalidUrlException::__construct / Uri\WhatWg\UrlValidationError::__construct |
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
Uri\Rfc3986\Uri::getSchemeПроверить протокол ссылки каталога до дальнейшей обработки.
getScheme(): ?stringПроверить протокол ссылки каталога до дальнейшей обработки.
Запись getScheme(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; имя схемы без двоеточия, нижний регистр; null у относительного URI. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- HTTPS://Shop.Example/p
- getScheme() выделяет компонент
- https
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('HTTPS://Shop.Example/p');
$value = $address->getScheme();
echo $value, "\n";
Ожидаемый результат
https
new создаёт объект из буквальной строки. $address->getScheme() читает один компонент; $value получает 'https', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('//shop.example/p');
echo $u->getScheme() === null ? "Нужна базовая схема\n" : "Схема есть\n";
Ожидаемый результат
Нужна базовая схема
Адрес с // содержит host, но ещё не задаёт схему. Не подставляйте https без политики приложения.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getRawScheme.
Uri\Rfc3986\Uri::getRawSchemeПоказать, как автор исходной ссылки записал схему.
getRawScheme(): ?stringПоказать, как автор исходной ссылки записал схему.
Запись getRawScheme(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; исходный регистр схемы без :, либо null. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- HTTPS://shop.example/p
- getRawScheme() выделяет компонент
- HTTPS
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('HTTPS://shop.example/p');
$value = $address->getRawScheme();
echo $value, "\n";
Ожидаемый результат
HTTPS
new создаёт объект из буквальной строки. $address->getRawScheme() читает один компонент; $value получает 'HTTPS', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('HtTpS://shop.example/p');
echo $u->getRawScheme(), ' -> ', $u->getScheme(), "\n";
Ожидаемый результат
HtTpS -> https
Raw полезен для диагностики представления. Решение «разрешён HTTPS» принимайте по нормализованной схеме, не по её исходному регистру.
Raw-компоненты не предназначены для обхода валидации. Для полного архива сохраняйте исходный текст отдельно; относительная база и последующие with-вызовы уже меняют адрес.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getScheme.
Uri\Rfc3986\Uri::getHostВыделить нормализованное имя узла ссылки, не спутать его с пользователем.
getHost(): ?stringВыделить нормализованное имя узла ссылки, не спутать его с пользователем.
Запись getHost(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; reg-name, IPv4 либо IPv6/IPFuture в квадратных скобках; null без authority, пустая строка при пустом host. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://trusted.example@Shop.Example/p
- getHost() выделяет компонент
- shop.example
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://trusted.example@Shop.Example/p');
$value = $address->getHost();
echo $value, "\n";
Ожидаемый результат
shop.example
new создаёт объект из буквальной строки. $address->getHost() читает один компонент; $value получает 'shop.example', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('mailto:sales@shop.example');
echo $u->getHost() === null ? "Host отсутствует\n" : $u->getHost();
echo $u->getPath(), "\n";
Ожидаемый результат
Host отсутствует
sales@shop.example
Знак @ внутри mailto не создаёт host-компонент. RFC не превращает Unicode-домен в IDNA; браузерный вариант — getAsciiHost.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getRawHost.
Uri\Rfc3986\Uri::getRawHostСопоставить введённое написание домена с нормализованным.
getRawHost(): ?stringСопоставить введённое написание домена с нормализованным.
Запись getRawHost(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; исходное написание host, IPv6 со скобками; null/пустая строка сохраняют различие. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://Sh%6fp.Example/p
- getRawHost() выделяет компонент
- Sh%6fp.Example
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://Sh%6fp.Example/p');
$value = $address->getRawHost();
echo $value, "\n";
Ожидаемый результат
Sh%6fp.Example
new создаёт объект из буквальной строки. $address->getRawHost() читает один компонент; $value получает 'Sh%6fp.Example', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://[2001:DB8::1]/');
echo $u->getRawHost(), "\n", $u->getHost(), "\n";
Ожидаемый результат
[2001:DB8::1]
[2001:db8::1]
Raw getter сохраняет эту запись IPv6; toRawString заново сериализует IPv6 развёрнуто. Поэтому raw не обещает побайтовый архив всей входной строки.
Raw-компоненты не предназначены для обхода валидации. Для полного архива сохраняйте исходный текст отдельно; относительная база и последующие with-вызовы уже меняют адрес.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getHost.
Uri\Rfc3986\Uri::getPathПолучить маршрут карточки после устранения навигационных сегментов.
getPath(): stringПолучить маршрут карточки после устранения навигационных сегментов.
Запись getPath(): string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | string; путь без query/fragment, иногда пустой; нормализованный, без полного percent-decoding. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/catalog/../%7ecard
- getPath() выделяет компонент
- /~card
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://shop.example/catalog/../%7ecard');
$value = $address->getPath();
echo $value, "\n";
Ожидаемый результат
/~card
new создаёт объект из буквальной строки. $address->getPath() читает один компонент; $value получает '/~card', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/items/a%2fb');
echo $u->getPath(), "\n";
echo $u->getPath() === '/items/a/b' ? "два сегмента\n" : "кодированный разделитель сохранён\n";
Ожидаемый результат
/items/a%2Fb
кодированный разделитель сохранён
После rawurldecode %2F стал бы настоящим /. Это меняет структуру; не декодируйте маршрут целиком перед проверкой правил доступа.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getRawPath.
Uri\Rfc3986\Uri::getRawPathРазобрать, почему две входные ссылки выглядят по-разному.
getRawPath(): stringРазобрать, почему две входные ссылки выглядят по-разному.
Запись getRawPath(): string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | string; путь до нормализации, пустой при его отсутствии; без query и fragment. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/catalog/../%7ecard
- getRawPath() выделяет компонент
- /catalog/../%7ecard
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://shop.example/catalog/../%7ecard');
$value = $address->getRawPath();
echo $value, "\n";
Ожидаемый результат
/catalog/../%7ecard
new создаёт объект из буквальной строки. $address->getRawPath() читает один компонент; $value получает '/catalog/../%7ecard', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/a/../price%20list');
echo $u->getRawPath(), "\n", $u->getPath(), "\n";
Ожидаемый результат
/a/../price%20list
/price%20list
Сохраняем исходный маршрут для локального разбора ошибки. Он не является безопасным путём файловой системы; %20 остаётся кодированным даже в normalized getter.
Raw-компоненты не предназначены для обхода валидации. Для полного архива сохраняйте исходный текст отдельно; относительная база и последующие with-вызовы уже меняют адрес.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getPath.
Uri\Rfc3986\Uri::getQueryОтделить параметры поиска от маршрута и якоря.
getQuery(): ?stringОтделить параметры поиска от маршрута и якоря.
Запись getQuery(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; query без первого ?, null если ? отсутствует, "" если запрос явно пуст. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/find?q=%7etool&part=a%2fb#top
- getQuery() выделяет компонент
- q=~tool&part=a%2Fb
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://shop.example/find?q=%7etool&part=a%2fb#top');
$value = $address->getQuery();
echo $value, "\n";
Ожидаемый результат
q=~tool&part=a%2Fb
new создаёт объект из буквальной строки. $address->getQuery() читает один компонент; $value получает 'q=~tool&part=a%2Fb', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
foreach (['https://shop.example/p', 'https://shop.example/p?', 'https://shop.example/p?0'] as $s) {
$q = (new Uri($s))->getQuery();
echo $q === null ? 'отсутствует' : ($q === '' ? 'пустой' : $q), "\n";
}
Ожидаемый результат
отсутствует
пустой
0
Строгие сравнения === сохраняют "0". Getter не разбирает пары параметров: parse_str нужен только если договор query — форма PHP, с учётом его преобразования ключей.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getRawQuery.
Uri\Rfc3986\Uri::getRawQueryСохранить написание query для проверки подписанного адреса.
getRawQuery(): ?stringСохранить написание query для проверки подписанного адреса.
Запись getRawQuery(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; query без ?, до нормализации; null и "" различаются. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/find?q=%7etool&part=a%2fb
- getRawQuery() выделяет компонент
- q=%7etool&part=a%2fb
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://shop.example/find?q=%7etool&part=a%2fb');
$value = $address->getRawQuery();
echo $value, "\n";
Ожидаемый результат
q=%7etool&part=a%2fb
new создаёт объект из буквальной строки. $address->getRawQuery() читает один компонент; $value получает 'q=%7etool&part=a%2fb', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/find?q=a+b&q=a%20b');
echo $u->getRawQuery(), "\n";
echo $u->getQuery(), "\n";
Ожидаемый результат
q=a+b&q=a%20b
q=a+b&q=a%20b
Ни +, ни порядок, ни повторные q не исчезли. Криптографическая подпись следует точному протоколу: даже raw URI не гарантирует сохранение всего исходного адреса; исходную строку храните отдельно.
Raw-компоненты не предназначены для обхода валидации. Для полного архива сохраняйте исходный текст отдельно; относительная база и последующие with-вызовы уже меняют адрес.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getQuery.
Uri\Rfc3986\Uri::getFragmentПолучить имя раздела для локальной навигации справочника.
getFragment(): ?stringПолучить имя раздела для локальной навигации справочника.
Запись getFragment(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; часть после # без самого #; нормализованная, null без #, "" при пустом #. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/help#%64elivery
- getFragment() выделяет компонент
- delivery
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://shop.example/help#%64elivery');
$value = $address->getFragment();
echo $value, "\n";
Ожидаемый результат
delivery
new создаёт объект из буквальной строки. $address->getFragment() читает один компонент; $value получает 'delivery', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/help#part%2f2');
echo $u->getFragment(), "\n";
echo $u->withFragment(null)->toString(), "\n";
Ожидаемый результат
part%2F2
https://shop.example/help
Fragment отделён от адреса ресурса; HTTP-клиенты обычно не передают его серверу. Существование такого id в HTML парсер не проверяет.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getRawFragment.
Uri\Rfc3986\Uri::getRawFragmentПоказать исходное кодирование якоря из импортированной ссылки.
getRawFragment(): ?stringПоказать исходное кодирование якоря из импортированной ссылки.
Запись getRawFragment(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; fragment без # до нормализации, null либо пустая строка по входу. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/help#%64elivery
- getRawFragment() выделяет компонент
- %64elivery
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://shop.example/help#%64elivery');
$value = $address->getRawFragment();
echo $value, "\n";
Ожидаемый результат
%64elivery
new создаёт объект из буквальной строки. $address->getRawFragment() читает один компонент; $value получает '%64elivery', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/help#part%2f2');
echo $u->getRawFragment(), ' -> ', $u->getFragment(), "\n";
Ожидаемый результат
part%2f2 -> part%2F2
Изменился только регистр hex в %2f. Для сравнения представлений выводим обе версии; это не извлечение текста HTML-раздела.
Raw-компоненты не предназначены для обхода валидации. Для полного архива сохраняйте исходный текст отдельно; относительная база и последующие with-вызовы уже меняют адрес.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getFragment.
Uri\Rfc3986\Uri::getUsernameРаспознать встроенные учётные данные перед показом ссылки.
getUsername(): ?stringРаспознать встроенные учётные данные перед показом ссылки.
Запись getUsername(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; часть userinfo до первого буквального :, нормализованная; "" если имя пусто, null если нет userinfo. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://%61nna:demo@shop.example/p
- getUsername() выделяет компонент
- anna
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://%61nna:demo@shop.example/p');
$value = $address->getUsername();
echo $value, "\n";
Ожидаемый результат
anna
new создаёт объект из буквальной строки. $address->getUsername() читает один компонент; $value получает 'anna', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
foreach (['https://shop.example/', 'https://:demo@shop.example/'] as $s) {
$name = (new Uri($s))->getUsername();
echo $name === null ? 'userinfo отсутствует' : ($name === '' ? 'имя пустое' : $name), "\n";
}
Ожидаемый результат
userinfo отсутствует
имя пустое
Пустое имя не значит отсутствие пароля. Не показывайте целый URI в публичном журнале; пример использует только вымышленные данные.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getRawUsername.
Uri\Rfc3986\Uri::getRawUsernameРазобрать исходную запись логина в диагностическом стенде.
getRawUsername(): ?stringРазобрать исходную запись логина в диагностическом стенде.
Запись getRawUsername(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; имя до первого буквального :, без нормализации; null без userinfo. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://%61nna:demo@shop.example/
- getRawUsername() выделяет компонент
- %61nna
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://%61nna:demo@shop.example/');
$value = $address->getRawUsername();
echo $value, "\n";
Ожидаемый результат
%61nna
new создаёт объект из буквальной строки. $address->getRawUsername() читает один компонент; $value получает '%61nna', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://team%3Awest:demo@shop.example/');
echo $u->getRawUsername(), "\n", $u->getUsername(), "\n";
Ожидаемый результат
team%3Awest
team%3Awest
%3A — часть имени, а не разделитель пароля. Нормализация не раскрывает зарезервированное двоеточие.
Raw-компоненты не предназначены для обхода валидации. Для полного архива сохраняйте исходный текст отдельно; относительная база и последующие with-вызовы уже меняют адрес.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getUsername.
Uri\Rfc3986\Uri::getPasswordПроверить наличие пароля в импортированной ссылке без его публикации.
getPassword(): ?stringПроверить наличие пароля в импортированной ссылке без его публикации.
Запись getPassword(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; часть userinfo после первого буквального :, нормализованная; "" у userinfo без пароля, null без userinfo. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://anna:d%65mo@shop.example/
- getPassword() выделяет компонент
- demo
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://anna:d%65mo@shop.example/');
$value = $address->getPassword();
echo $value, "\n";
Ожидаемый результат
demo
new создаёт объект из буквальной строки. $address->getPassword() читает один компонент; $value получает 'demo', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
foreach (['https://shop.example/', 'https://anna@shop.example/'] as $s) {
$password = (new Uri($s))->getPassword();
echo $password === null ? 'userinfo отсутствует' : ($password === '' ? 'пароль пустой' : 'пароль задан'), "\n";
}
Ожидаемый результат
userinfo отсутствует
пароль пустой
Значение выводится только в первом учебном вызове с фиктивным demo. В реальном журнале выводите признак, не секрет; SensitiveParameter не скрывает возвращённую строку.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getRawPassword.
Uri\Rfc3986\Uri::getRawPasswordПроверить кодирование тестового пароля, не меняя его структуру.
getRawPassword(): ?stringПроверить кодирование тестового пароля, не меняя его структуру.
Запись getRawPassword(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; хвост userinfo после первого буквального :, до нормализации; null/"" как у getPassword. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://anna:d%65mo@shop.example/
- getRawPassword() выделяет компонент
- d%65mo
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://anna:d%65mo@shop.example/');
$value = $address->getRawPassword();
echo $value, "\n";
Ожидаемый результат
d%65mo
new создаёт объект из буквальной строки. $address->getRawPassword() читает один компонент; $value получает 'd%65mo', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://anna:demo%3Awest@shop.example/');
echo $u->getRawPassword(), "\n", $u->getPassword(), "\n";
Ожидаемый результат
demo%3Awest
demo%3Awest
Кодированное двоеточие остаётся частью пароля. URI не подтверждает эти учётные данные и не выполняет вход.
Raw-компоненты не предназначены для обхода валидации. Для полного архива сохраняйте исходный текст отдельно; относительная база и последующие with-вызовы уже меняют адрес.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getPassword.
Uri\Rfc3986\Uri::getUserInfoУдалить весь блок учётных данных, не перепутав его с host.
getUserInfo(): ?stringУдалить весь блок учётных данных, не перепутав его с host.
Запись getUserInfo(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; весь userinfo без @, нормализованный; null без блока, "" при @ без содержимого. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://%61nna:d%65mo@shop.example/
- getUserInfo() выделяет компонент
- anna:demo
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://%61nna:d%65mo@shop.example/');
$value = $address->getUserInfo();
echo $value, "\n";
Ожидаемый результат
anna:demo
new создаёт объект из буквальной строки. $address->getUserInfo() читает один компонент; $value получает 'anna:demo', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://anna:demo@shop.example/p');
echo $u->getUserInfo() !== null ? "Нужна очистка\n" : "Без userinfo\n";
echo $u->withUserInfo(null)->toString(), "\n";
Ожидаемый результат
Нужна очистка
https://shop.example/p
Удаление userinfo не удаляет токены из query/path. Для журналов нужен собственный разрешённый набор полей.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getRawUserInfo.
Uri\Rfc3986\Uri::getRawUserInfoСопоставить исходный и нормализованный блок учётных данных на фиктивной строке.
getRawUserInfo(): ?stringСопоставить исходный и нормализованный блок учётных данных на фиктивной строке.
Запись getRawUserInfo(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; userinfo без @, до нормализации; не равен всему authority. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://%61nna:d%65mo@shop.example/
- getRawUserInfo() выделяет компонент
- %61nna:d%65mo
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://%61nna:d%65mo@shop.example/');
$value = $address->getRawUserInfo();
echo $value, "\n";
Ожидаемый результат
%61nna:d%65mo
new создаёт объект из буквальной строки. $address->getRawUserInfo() читает один компонент; $value получает '%61nna:d%65mo', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://team%3Awest:demo@shop.example/');
echo $u->getRawUserInfo(), "\n", $u->getUsername(), "\n";
Ожидаемый результат
team%3Awest:demo
team%3Awest
Authority включает также host и port. Разделение логина и пароля происходит по буквальному :, не по %3A.
Raw-компоненты не предназначены для обхода валидации. Для полного архива сохраняйте исходный текст отдельно; относительная база и последующие with-вызовы уже меняют адрес.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getUserInfo.
Uri\Rfc3986\Uri::getPortПоказать явно записанный порт учебного сервиса.
getPort(): ?intПоказать явно записанный порт учебного сервиса.
Запись getPort(): ?int описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?int; десятичное число без :, null если порт отсутствует или его текст пуст. RFC не удаляет стандартный порт. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example:08443/p
- getPort() выделяет компонент
- 8443
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://shop.example:08443/p');
$value = $address->getPort();
echo $value, "\n";
Ожидаемый результат
8443
new создаёт объект из буквальной строки. $address->getPort() читает один компонент; $value получает '8443', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example:70000/');
$port = $u->getPort();
echo $port, "\n";
echo $port !== null && $port > 65535 ? "Не сетевой порт TCP/UDP\n" : "В диапазоне\n";
Ожидаемый результат
70000
Не сетевой порт TCP/UDP
В PHP 8.5.11 RFC принимает неотрицательный порт, помещающийся в PHP int; это шире сетевого диапазона. Парсер WHATWG ограничивает порт 65535. Очень длинная цифровая запись тоже может быть отвергнута реализацией.
Нормализация RFC: схема/host в нижний регистр; hex в %-последовательностях в верхний; percent-encoded unreserved ASCII раскодируются; в пути убираются сегменты «.» и «..». Зарезервированный %2F не превращается в разделитель /. Это не полная URL-декодировка и не проверка доступности сервера.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::toString.
Uri\WhatWg\Url::getSchemeОпределить схему готовой браузерной ссылки.
getScheme(): stringОпределить схему готовой браузерной ссылки.
Запись getScheme(): string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | string; всегда схема без :, нижний регистр. У корректного WHATWG Url нет отсутствующей схемы. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- HTTPS://shop.example/p
- getScheme() выделяет компонент
- https
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('HTTPS://shop.example/p');
$value = $address->getScheme();
echo $value, "\n";
Ожидаемый результат
https
new создаёт объект из буквальной строки. $address->getScheme() читает один компонент; $value получает 'https', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('mailto:sales@shop.example');
echo $u->getScheme() === 'https' ? "Веб-ссылка\n" : "Другая схема\n";
Ожидаемый результат
Другая схема
Валидный URL не обязательно HTTP(S). Проверяйте нужную схему отдельно; не открывайте произвольные схемы через внешние программы.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::getAsciiHostПолучить форму международного домена для машинного сравнения.
getAsciiHost(): ?stringПолучить форму международного домена для машинного сравнения.
Запись getAsciiHost(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; сериализованный host: ASCII/IDNA для доменов, IPv4 либо IPv6 со скобками; null если host отсутствует, "" при пустом host. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://пример.рф/catalog
- getAsciiHost() выделяет компонент
- xn--e1afmkfd.xn--p1ai
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://пример.рф/catalog');
$value = $address->getAsciiHost();
echo $value, "\n";
Ожидаемый результат
xn--e1afmkfd.xn--p1ai
new создаёт объект из буквальной строки. $address->getAsciiHost() читает один компонент; $value получает 'xn--e1afmkfd.xn--p1ai', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('http://0x7f.1/');
echo $u->getAsciiHost(), "\n";
echo (new Url('mailto:sales@shop.example'))->getAsciiHost() === null ? "Нет host\n" : "Есть host\n";
Ожидаемый результат
127.0.0.1
Нет host
WHATWG распознаёт альтернативную запись IPv4. Проверка безопасности по исходному тексту host ненадёжна. Getter не выполняет DNS.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::getUnicodeHostПоказать человеку международный домен, сохранив машинную форму рядом.
getUnicodeHost(): ?stringПоказать человеку международный домен, сохранив машинную форму рядом.
Запись getUnicodeHost(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; домен в Unicode; IPv4/IPv6 остаются адресами, null без host. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://xn--e1afmkfd.xn--p1ai/p
- getUnicodeHost() выделяет компонент
- пример.рф
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://xn--e1afmkfd.xn--p1ai/p');
$value = $address->getUnicodeHost();
echo $value, "\n";
Ожидаемый результат
пример.рф
new создаёт объект из буквальной строки. $address->getUnicodeHost() читает один компонент; $value получает 'пример.рф', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://пример.рф/p');
echo $u->getUnicodeHost(), "\n", $u->getAsciiHost(), "\n";
Ожидаемый результат
пример.рф
xn--e1afmkfd.xn--p1ai
Визуально похожие буквы разных алфавитов не доказывают доверие. Для HTML экранируйте отображаемую строку; машинный allowlist сравнивает каноническое ASCII-имя.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::getPathВыделить маршрут карточки после браузерного разбора.
getPath(): stringВыделить маршрут карточки после браузерного разбора.
Запись getPath(): string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | string; pathname, у HTTP(S) пустой путь обычно сериализуется как /. Percent-encoding не декодируется полностью. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/a/../price%20list
- getPath() выделяет компонент
- /price%20list
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://shop.example/a/../price%20list');
$value = $address->getPath();
echo $value, "\n";
Ожидаемый результат
/price%20list
new создаёт объект из буквальной строки. $address->getPath() читает один компонент; $value получает '/price%20list', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/%7ecard/a%2fb');
echo $u->getPath(), "\n";
echo (new Url('https://shop.example'))->getPath(), "\n";
Ожидаемый результат
/%7ecard/a%2fb
/
WHATWG не применяет к percent-encoding те же правила нормализации, что RFC getter: %7e и регистр %2f здесь сохраняются. Это URL-путь, не путь к локальному файлу.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::getQueryИзвлечь строку поисковых параметров без смешения с якорем.
getQuery(): ?stringИзвлечь строку поисковых параметров без смешения с якорем.
Запись getQuery(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; query без ?, null если отсутствует, "" если введён голый ?; + остаётся +. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/find?q=%7etool&part=a%2fb#top
- getQuery() выделяет компонент
- q=%7etool&part=a%2fb
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://shop.example/find?q=%7etool&part=a%2fb#top');
$value = $address->getQuery();
echo $value, "\n";
Ожидаемый результат
q=%7etool&part=a%2fb
new создаёт объект из буквальной строки. $address->getQuery() читает один компонент; $value получает 'q=%7etool&part=a%2fb', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
foreach (['https://shop.example/p', 'https://shop.example/p?', 'https://shop.example/p?0'] as $s) {
$q = (new Url($s))->getQuery();
echo $q === null ? 'отсутствует' : ($q === '' ? 'пустой' : $q), "\n";
}
Ожидаемый результат
отсутствует
пустой
0
Не используйте if ($q) для проверки наличия: строка "0" ложна. Разбор в поля — отдельный договор, getter оставляет повторы параметров.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::getFragmentПолучить браузерный якорь для карточки раздела.
getFragment(): ?stringПолучить браузерный якорь для карточки раздела.
Запись getFragment(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; fragment без #, null без fragment и "" при голом #; %-кодирование сохраняется. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example/help#part%202
- getFragment() выделяет компонент
- part%202
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://shop.example/help#part%202');
$value = $address->getFragment();
echo $value, "\n";
Ожидаемый результат
part%202
new создаёт объект из буквальной строки. $address->getFragment() читает один компонент; $value получает 'part%202', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/help#%64elivery');
echo $u->getFragment(), "\n";
echo $u->withFragment(null)->toAsciiString(), "\n";
Ожидаемый результат
%64elivery
https://shop.example/help
Отделили якорь для списка ресурсов. Сервер HTTP не получает fragment как часть request target; UI должен отдельно проверять существование раздела.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::getUsernameОпределить, что ссылка содержит встроенное имя пользователя.
getUsername(): ?stringОпределить, что ссылка содержит встроенное имя пользователя.
Запись getUsername(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; percent-encoded username; null когда и username, и password пусты, "" при пустом имени и непустом пароле. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://%61nna:demo@shop.example/
- getUsername() выделяет компонент
- %61nna
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://%61nna:demo@shop.example/');
$value = $address->getUsername();
echo $value, "\n";
Ожидаемый результат
%61nna
new создаёт объект из буквальной строки. $address->getUsername() читает один компонент; $value получает '%61nna', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
foreach (['https://@shop.example/', 'https://:demo@shop.example/'] as $s) {
$name = (new Url($s))->getUsername();
echo $name === null ? 'credentials отсутствуют' : ($name === '' ? 'имя пустое' : 'имя задано'), "\n";
}
Ожидаемый результат
credentials отсутствуют
имя пустое
WHATWG удаляет пустой @ при сериализации. Это отличается от RFC userinfo. Не путайте отсутствие имени с отсутствием всех credentials.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::getPasswordОтметить пароль в импортируемой ссылке и скрыть его в отчёте.
getPassword(): ?stringОтметить пароль в импортируемой ссылке и скрыть его в отчёте.
Запись getPassword(): ?string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?string; percent-encoded password, null когда оба поля credentials пусты, "" если задано лишь имя. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://anna:d%65mo@shop.example/
- getPassword() выделяет компонент
- d%65mo
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://anna:d%65mo@shop.example/');
$value = $address->getPassword();
echo $value, "\n";
Ожидаемый результат
d%65mo
new создаёт объект из буквальной строки. $address->getPassword() читает один компонент; $value получает 'd%65mo', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
foreach (['https://shop.example/', 'https://anna@shop.example/'] as $s) {
$password = (new Url($s))->getPassword();
echo $password === null ? 'нет credentials' : ($password === '' ? 'пароль пустой' : 'пароль скрыт'), "\n";
}
Ожидаемый результат
нет credentials
пароль пустой
Демонстрационный demo не является настоящим секретом. Getter и debugInfo не редактируют пароль автоматически; SensitiveParameter защищает аргумент трассировки, а не содержимое объекта.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::getPortРазличить стандартный и явно нестандартный порт веб-сервиса.
getPort(): ?intРазличить стандартный и явно нестандартный порт веб-сервиса.
Запись getPort(): ?int описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет; () обязательны. |
| Результат | ?int; явный нестандартный порт 0…65535, null при отсутствии или стандартном порте данной special-схемы. |
| Ошибки | Для уже корректно созданного объекта обычного отказа данных нет. Неверное число/тип аргументов нарушает договор PHP; ошибки создания рассматриваются отдельно. Внутренний сбой RFC-парсера может дать Uri\UriError, это не «компонент отсутствует». |
| Версия | PHP 8.5.0; расширение uri. |
- https://shop.example:8443/p
- getPort() выделяет компонент
- 8443
- $value → echo, без HTTP
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://shop.example:8443/p');
$value = $address->getPort();
echo $value, "\n";
Ожидаемый результат
8443
new создаёт объект из буквальной строки. $address->getPort() читает один компонент; $value получает '8443', echo печатает его. URI не открывается и сетевого запроса нет.
Теперь проверим границу, важную при обработке импортированных ссылок. В следующем примере === сравнивает значение вместе с типом; foreach, если используется, проходит по небольшому списку адресов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
foreach (['https://shop.example:443/', 'http://shop.example:80/', 'https://shop.example:8443/'] as $s) {
$u = new Url($s);
echo $u->getPort() === null ? 'стандартный/не указан' : $u->getPort(), "\n";
}
Ожидаемый результат
стандартный/не указан
стандартный/не указан
8443
null не означает отсутствие сетевого порта у протокола; канонизация убрала стандартное число. Если приложению нужен эффективный порт, задайте явную таблицу разрешённых схем.
Это компоненты WHATWG URL, а не необработанный ввод. Канонизация уже произошла при создании. Возвращённую строку нельзя автоматически считать безопасной для HTML, shell или исходящего HTTP-запроса.
Практика: замените адрес на ссылку вашего учебного каталога без учётных данных, предскажите значение этого компонента и проверьте его. Не обращайтесь к сети: сравните только строки.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\Rfc3986\Uri::__constructРазобрать ссылку один раз и затем работать с её компонентами.
__construct(string $uri, ?Uri\Rfc3986\Uri $baseUrl = null)Разобрать ссылку один раз и затем работать с её компонентами.
Запись __construct(string $uri, ?Uri\Rfc3986\Uri $baseUrl = null) описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $uri | Строка адреса/ссылки. Не выполняется HTTP-запрос. |
| $baseUrl = null | Объект Uri\Rfc3986\Uri для разрешения относительной ссылки, не строка. Без базы допускается относительный URI. Переданная база должна быть абсолютной; в PHP 8.5.11 это проверяется даже при абсолютном первом аргументе. |
| Результат | new возвращает новый final readonly объект; сам __construct не возвращает отдельного результата. |
| Отказ | Uri\InvalidUriException при неверном URI или относительной базе; Uri\UriError при внутреннем отказе. |
- https://shop.example/help/delivery
- new проверяет и разбирает строку
- $address хранит объект
- getter возвращает shop.example
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$address = new Uri('https://shop.example/help/delivery');
echo $address->getHost(), "\n";
Ожидаемый результат
shop.example
new вызывает конструктор и сохраняет объект в $address. Метод затем читает host. Класс находится в namespace; use даёт короткое имя только в этом файле.
Относительная ссылка имеет смысл в контексте другой страницы. Последний сегмент базового URL без завершающего / считается именем ресурса. Следующий вызов не читает ни файл, ни сайт.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$base = new Uri('https://shop.example/catalog/tools/list');
$card = new Uri('../drill', $base);
echo $card->toString(), "\n";
echo $base->toString(), "\n";
Ожидаемый результат
https://shop.example/catalog/drill
https://shop.example/catalog/tools/list
Сначала отбрасывается list, затем .. поднимает на один каталог. База остаётся прежним объектом. Для операций над файловой системой нужен иной API.
Повторно вызывать __construct на готовом readonly объекте нельзя: Error. Создайте другой объект или используйте with-метод. Ошибка PHP-типа даёт TypeError, а не null. Перед первым вызовом на старом PHP проверьте class_exists; библиотека присутствует с PHP 8.5.0.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::parse, Uri\Rfc3986\Uri::resolve.
Uri\Rfc3986\Uri::parseПроверить массовый импорт ссылок, где неправильная запись ожидаема.
static parse(string $uri, ?Uri\Rfc3986\Uri $baseUrl = null): ?staticПроверить массовый импорт ссылок, где неправильная запись ожидаема.
Запись static parse(string $uri, ?Uri\Rfc3986\Uri $baseUrl = null): ?static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $uri, $baseUrl | Как у конструктора: строка ссылки и необязательный объект того же класса; чужой класс — TypeError. |
| Результат | ?static: новый Uri либо null при отказе парсинга. Не false. |
| Ошибки | Ошибки PHP-типов/аргументов не подавляются. null — только обычная невозможность разобрать/разрешить адрес. |
- Строка https://shop.example/help
- Uri::parse(...)
- Объект или null
- Проверка !== null → вывод адреса
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$candidate = Uri::parse('https://shop.example/help');
if ($candidate !== null) {
echo $candidate->toString(), "\n";
} else {
echo "Исправьте адрес\n";
}
Ожидаемый результат
https://shop.example/help
:: вызывает статический метод класса, без предварительного new. !== null отделяет успех от ошибки до вызова метода у объекта.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
foreach (['https://shop.example/%ZZ', '../help'] as $text) {
$u = Uri::parse($text);
echo $u === null ? 'ошибка синтаксиса' : $u->toString(), "\n";
}
Ожидаемый результат
ошибка синтаксиса
../help
RFC принимает относительный URI без базы. Поэтому «parse не null» ещё не означает готовую абсолютную веб-ссылку; для такой политики отдельно нужны схема и host.
Следующий шаг: решите заранее, допустимы ли относительные ссылки и автоматические исправления. Не заменяйте null исходной непроверенной строкой, если дальше ожидается проверенный объект.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::__construct.
Uri\Rfc3986\Uri::resolveВосстановить полную ссылку из относительной записи HTML или конфигурации.
resolve(string $uri): staticВосстановить полную ссылку из относительной записи HTML или конфигурации.
Запись resolve(string $uri): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $uri | Относительный или абсолютный адрес; текущий объект — база. |
| Результат | Новый объект, исходный не меняется. |
| Отказ | Uri\InvalidUriException для ошибочной ссылки/относительной базы. |
- База https://shop.example/help/start
- resolve("delivery")
- Заменить start на delivery
- https://shop.example/help/delivery
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$page = new Uri('https://shop.example/help/start');
$delivery = $page->resolve('delivery');
echo $delivery->toString(), "\n";
Ожидаемый результат
https://shop.example/help/delivery
Текущий объект становится базой, resolve получает только относительную строку. delivery заменяет start; существующий $page не изменяется.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$base = new Uri('https://shop.example/help/');
foreach (['delivery', '/catalog', '//cdn.example/image.png'] as $text) {
echo $base->resolve($text)->toString(), "\n";
}
Ожидаемый результат
https://shop.example/help/delivery
https://shop.example/catalog
https://cdn.example/image.png
Ссылка /catalog начинается от корня узла; //cdn.example наследует только схему и заменяет host. Поэтому «разрешить относительно доверенной базы» не ограничивает результат тем же доменом.
База должна быть абсолютной. Относительный объект Uri допустим как значение, но вызов resolve у такого объекта даёт Uri\InvalidUriException. Не путайте нормализацию . и .. с существованием каталогов на диске.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::__construct, Uri\Rfc3986\Uri::withPath.
Uri\Rfc3986\Uri::equalsУбрать дубликаты ссылок по явно выбранному правилу fragment.
equals(Uri\Rfc3986\Uri $uri, Uri\UriComparisonMode $comparisonMode = Uri\UriComparisonMode::ExcludeFragment): boolУбрать дубликаты ссылок по явно выбранному правилу fragment.
Запись equals(Uri\Rfc3986\Uri $uri, Uri\UriComparisonMode $comparisonMode = Uri\UriComparisonMode::ExcludeFragment): bool описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Первый аргумент | Объект того же класса; другой класс — TypeError. |
| $comparisonMode | IncludeFragment или ExcludeFragment (по умолчанию), не строка и не bool. |
| Результат | bool; совпадение нормализованных сериализаций по выбранному режиму. |
| Ограничение | Не гарантирует одинаковый ресурс/ответ, владение доменом или безопасное соединение. |
- help#payment и help#delivery
- Режим по умолчанию исключает fragment
- true → Один ресурс
- IncludeFragment → false
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$a = new Uri('https://shop.example/help#payment');
$b = new Uri('https://shop.example/help#delivery');
echo $a->equals($b) ? "Один ресурс\n" : "Разные адреса\n";
echo $a->equals($b, \Uri\UriComparisonMode::IncludeFragment) ? "Один якорь\n" : "Разные якоря\n";
Ожидаемый результат
Один ресурс
Разные якоря
По умолчанию ExcludeFragment игнорирует fragment. IncludeFragment учитывает его. Это выбранный алгоритм эквивалентности строк, не доказательство одинакового ответа сервера.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$a = new Uri('https://shop.example:443/');
$b = new Uri('https://shop.example/');
echo $a->equals($b) ? "эквивалентны\n" : "различаются\n";
Ожидаемый результат
различаются
WHATWG удаляет стандартный порт; RFC в этом API сохраняет его. Выбирайте один парсер для формирования и сравнения ключей, не чередуйте правила между проверкой и использованием.
equals сравнивает нормализованные сериализации выбранного класса, с учётом режима fragment. Это не === (тождество объектов), не DNS, не сравнение содержимого сайта. Порядок query-пар не сортируется; HTTP-сервер может придавать ему значение.
Перед первым запуском: как устроен вызов и что он проверяет.
Uri\Rfc3986\Uri::toStringПодготовить разобранный адрес к хранению или отображению в нужной форме.
toString(): stringПодготовить разобранный адрес к хранению или отображению в нужной форме.
Запись toString(): string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет. |
| Результат | string; нормализованная сериализация RFC; не удаляет стандартный порт HTTP. |
| Отказ | При внутренней невозможности RFC-сериализации возможен Uri\UriError; тип/число аргументов проверяет PHP. Обычная валидная строка возвращается без сети. |
- HTTPS://Shop.Example/a/../%7ecard
- toString() выбирает форму
- https://shop.example/~card
- Перед HTML отдельное экранирование
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('HTTPS://Shop.Example/a/../%7ecard');
echo $u->toString(), "\n";
Ожидаемый результат
https://shop.example/~card
Raw сохраняет эту исходную запись компонентов; normalized сводит регистр, unreserved percent и путь. Строка по-прежнему не является HTTP-запросом.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://[2001:DB8::1]/');
echo $u->toString(), "\n";
Ожидаемый результат
https://[2001:0db8:0000:0000:0000:0000:0000:0001]/
Даже toRawString повторно сериализует IPv6 полностью развёрнутыми строчными hex-группами. Для аудита точного исходного текста или протокола подписи храните входную строку отдельно.
У этих классов нет __toString: используйте явный метод вместо echo $u или (string) $u. Полученная строка может содержать credentials, query-токены и персональные данные. Полный var_dump/журнал допустим только на фиктивных тестовых адресах.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::toRawString.
Uri\Rfc3986\Uri::toRawStringПодготовить разобранный адрес к хранению или отображению в нужной форме.
toRawString(): stringПодготовить разобранный адрес к хранению или отображению в нужной форме.
Запись toRawString(): string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет. |
| Результат | string; сериализация исходных компонентов, с исключением канонической записи IPv6. |
| Отказ | При внутренней невозможности RFC-сериализации возможен Uri\UriError; тип/число аргументов проверяет PHP. Обычная валидная строка возвращается без сети. |
- HTTPS://Shop.Example/a/../%7ecard
- toRawString() выбирает форму
- HTTPS://Shop.Example/a/../%7ecard
- Перед HTML отдельное экранирование
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('HTTPS://Shop.Example/a/../%7ecard');
echo $u->toRawString(), "\n";
Ожидаемый результат
HTTPS://Shop.Example/a/../%7ecard
Raw сохраняет эту исходную запись компонентов; normalized сводит регистр, unreserved percent и путь. Строка по-прежнему не является HTTP-запросом.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://[2001:DB8::1]/');
echo $u->toRawString(), "\n";
Ожидаемый результат
https://[2001:0db8:0000:0000:0000:0000:0000:0001]/
Даже toRawString повторно сериализует IPv6 полностью развёрнутыми строчными hex-группами. Для аудита точного исходного текста или протокола подписи храните входную строку отдельно.
У этих классов нет __toString: используйте явный метод вместо echo $u или (string) $u. Полученная строка может содержать credentials, query-токены и персональные данные. Полный var_dump/журнал допустим только на фиктивных тестовых адресах.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::toString.
Uri\Rfc3986\Uri::__debugInfoРазобрать локальную ошибку, не отдавая полный URI пользователю.
__debugInfo(): arrayРазобрать локальную ошибку, не отдавая полный URI пользователю.
Запись __debugInfo(): array описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет. |
| Результат | array с состоянием для диагностики. PHP 8.5.11 показывает scheme, username, password, host, port, path, query, fragment; это не публичные writable properties. |
| Ограничение | Формат диагностики не используйте как долговременный формат обмена. Наличие SensitiveParameter у setter не скрывает debugInfo. |
- Объект https://shop.example:8443/p
- __debugInfo() → массив
- $state["host"] → shop.example
- $state["port"] → 8443
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example:8443/p?q=1#top');
$state = $u->__debugInfo();
echo $state['host'], "\n", $state['port'], "\n";
Ожидаемый результат
shop.example
8443
__debugInfo возвращает массив, который использует var_dump. Прямой вызов показан ради договора; для бизнес-логики предпочтительнее getter.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://anna:demo@shop.example/p?token=demo');
$state = $u->__debugInfo();
echo $state['scheme'], '://', $state['host'], "\n";
echo array_key_exists('password', $state) ? "Полный dump содержит поле пароля\n" : "Нет поля\n";
Ожидаемый результат
https://shop.example
Полный dump содержит поле пароля
Только scheme и host попали в сообщение. Даже путь может содержать токен, поэтому нет универсальной функции «безопасно логировать весь URL». demo — фиктивные значения.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getScheme.
Uri\Rfc3986\Uri::__serializeПоддержать локальное сохранение объекта стандартным механизмом PHP.
__serialize(): arrayПоддержать локальное сохранение объекта стандартным механизмом PHP.
Запись __serialize(): array описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет. |
| Результат | array состояния, который serialize() затем кодирует в PHP-строку. |
| PHP 8.5.11 | Внутренний формат [["uri" => строка], []]: RFC сохраняет raw-сериализацию, WHATWG — ASCII. Не редактируйте формат вручную и не обещайте его стабильность другим реализациям. |
| Ограничение | Credentials и query могут попасть в состояние. Нет шифрования или подписи; сеть не используется. |
- https://shop.example/help
- __serialize()
- Массив для механизма PHP
- Исходный объект прежний
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/help');
$data = $u->__serialize();
echo is_array($data) ? "Массив состояния\n" : "Другой тип\n";
echo $u->toString(), "\n";
Ожидаемый результат
Массив состояния
https://shop.example/help
__serialize возвращает array, не текст serialize(). Упоминание string в Return Values текущей страницы PHP.net противоречит её же сигнатуре; здесь используется подтверждённый тип PHP 8.5.11.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/help#delivery');
$bytes = serialize($u);
$copy = unserialize($bytes, ['allowed_classes' => [Uri::class]]);
echo $copy instanceof Uri && $copy->equals($u, \Uri\UriComparisonMode::IncludeFragment) ? "Адрес восстановлен\n" : "Ошибка\n";
echo $copy === $u ? "Тот же объект\n" : "Другой объект\n";
Ожидаемый результат
Адрес восстановлен
Другой объект
Обе стороны контролируем мы, ввод не внешний. allowed_classes не делает недоверенную PHP-сериализацию безопасной. Для внешнего обмена удобнее строка URL в JSON и повторный parse с вашей политикой.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::__unserialize.
Uri\Rfc3986\Uri::__unserializeПонять восстановление состояния и корректно обработать его отказ.
__unserialize(array $data): voidПонять восстановление состояния и корректно обработать его отказ.
Запись __unserialize(array $data): void описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $data | Массив, подготовленный __serialize для этого класса. Не JSON и не готовая строка serialize(). |
| Результат | void; заполняет только ещё не инициализированный объект в механизме восстановления. |
| Отказ PHP 8.5.11 | Неверная структура/строка URI → Exception; повторная инициализация → Exception с предыдущим Error. Уточнение по исходнику и отдельному запуску, не замена всех ошибок Uri на Exception. |
- Свой объект → serialize
- PHP-строка только в памяти
- unserialize создаёт новый объект
- __unserialize восстанавливает → getter
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$saved = serialize(new Uri('https://shop.example/help'));
$restored = unserialize($saved, ['allowed_classes' => [Uri::class]]);
if ($restored instanceof Uri) {
echo $restored->toString(), "\n";
}
Ожидаемый результат
https://shop.example/help
unserialize создаёт ещё не инициализированный объект и передаёт его состояние в __unserialize. Прикладной код не должен повторно инициализировать уже готовый readonly объект.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/help');
try {
$u->__unserialize($u->__serialize());
} catch (Throwable $e) {
echo $e::class, "\n";
echo $e->getPrevious() instanceof Error ? "Предыдущая причина: Error\n" : "Другая причина\n";
}
echo $u->toString(), "\n";
Ожидаемый результат
Exception
Предыдущая причина: Error
https://shop.example/help
Точно наблюдаемая PHP 8.5.11 цепочка: внешний Exception, предыдущий Error о readonly. Поэтому catch(Error) один не ловит этот конкретный вызов. Вызываем Throwable только на узкой границе учебной диагностики, не подавляем все ошибки приложения.
Текущая страница PHP.net описывает Error для повторного вызова и InvalidUriException для неправильного URI. Tagged PHP 8.5.11 в этом hook оборачивает readonly Error в Exception, а неправильную структуру/URI отклоняет обычным Exception. Это версионное расхождение явно отделено от правил parse/конструктора. При неверном типе $data возникает TypeError. Не собирайте сериализованные payload из внешнего ввода.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::__serialize, Uri\Rfc3986\Uri::parse.
Uri\WhatWg\Url::__constructРазобрать ссылку один раз и затем работать с её компонентами.
__construct(string $uri, ?Uri\WhatWg\Url $baseUrl = null, &$softErrors = null)Разобрать ссылку один раз и затем работать с её компонентами.
Запись __construct(string $uri, ?Uri\WhatWg\Url $baseUrl = null, &$softErrors = null) описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $uri | Строка адреса/ссылки. Не выполняется HTTP-запрос. |
| $baseUrl = null | Объект Uri\WhatWg\Url для разрешения относительной ссылки, не строка. Для относительного ввода база обязательна. Абсолютный URL обычно задаёт собственный адрес; особые same-scheme ссылки следуют алгоритму WHATWG. |
| &$softErrors | Необязательная выходная переменная, заполняемая списком UrlValidationError; успешный вызов заменяет её, в том числе на []. При жёстком отказе смотрите exception->errors: эта выходная переменная не обновляется. Знак & — запись результата в переданную переменную. |
| Результат | new возвращает новый final readonly объект; сам __construct не возвращает отдельного результата. |
| Отказ | Uri\WhatWg\InvalidUrlException с массивом errors. |
- https://shop.example/help/delivery
- new проверяет и разбирает строку
- $address хранит объект
- getter возвращает shop.example
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$address = new Url('https://shop.example/help/delivery');
echo $address->getAsciiHost(), "
";
Ожидаемый результат
shop.example
new вызывает конструктор и сохраняет объект в $address. Метод затем читает host. Класс находится в namespace; use даёт короткое имя только в этом файле.
Относительная ссылка имеет смысл в контексте другой страницы. Последний сегмент базового URL без завершающего / считается именем ресурса. Следующий вызов не читает ни файл, ни сайт.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$base = new Url('https://shop.example/catalog/tools/list');
$card = new Url('../drill', $base);
echo $card->toAsciiString(), "\n";
echo $base->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/catalog/drill
https://shop.example/catalog/tools/list
Сначала отбрасывается list, затем .. поднимает на один каталог. База остаётся прежним объектом. Для операций над файловой системой нужен иной API.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$warnings = [];
$u = new Url('https:shop.example', null, $warnings);
echo $u->toAsciiString(), "\n";
echo $warnings[0]->type->name, ':', $warnings[0]->failure ? 'hard' : 'soft', "\n";
Ожидаемый результат
https://shop.example/
SpecialSchemeMissingFollowingSolidus:soft
Успешный объект не означает отсутствие ошибок в исходной записи. Программа может принять автоматическое исправление либо отклонить любой soft error согласно своим требованиям; тип enum полезнее нестабильного текста сообщения.
Повторно вызывать __construct на готовом readonly объекте нельзя: Error. Создайте другой объект или используйте with-метод. Ошибка PHP-типа даёт TypeError, а не null. Перед первым вызовом на старом PHP проверьте class_exists; библиотека присутствует с PHP 8.5.0.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::parse, Uri\WhatWg\Url::resolve.
Uri\WhatWg\Url::parseПроверить массовый импорт ссылок, где неправильная запись ожидаема.
static parse(string $uri, ?Uri\WhatWg\Url $baseUrl = null, &$errors = null): ?staticПроверить массовый импорт ссылок, где неправильная запись ожидаема.
Запись static parse(string $uri, ?Uri\WhatWg\Url $baseUrl = null, &$errors = null): ?static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $uri, $baseUrl | Как у конструктора: строка ссылки и необязательный объект того же класса; чужой класс — TypeError. |
| &$errors | Выходная переменная: список мягких/жёстких UrlValidationError. Заменяется результатом текущего разбора, даже при null. Передайте переменную, а не литерал []. |
| Результат | ?static: новый Url либо null при отказе парсинга. Не false. |
| Ошибки | Ошибки PHP-типов/аргументов не подавляются. null — только обычная невозможность разобрать/разрешить адрес. |
- Строка https://shop.example/help
- Url::parse(...)
- Объект или null
- Проверка !== null → вывод адреса
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$candidate = Url::parse('https://shop.example/help');
if ($candidate !== null) {
echo $candidate->toAsciiString(), "\n";
} else {
echo "Исправьте адрес\n";
}
Ожидаемый результат
https://shop.example/help
:: вызывает статический метод класса, без предварительного new. !== null отделяет успех от ошибки до вызова метода у объекта.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$errors = [];
$candidate = Url::parse('https://shop.example:70000/', null, $errors);
echo $candidate === null ? "Адрес отклонён\n" : "Адрес принят\n";
echo $errors[0]->type->name, ':', $errors[0]->failure ? 'hard' : 'soft', "\n";
Ожидаемый результат
Адрес отклонён
PortOutOfRange:hard
parse возвращает null, но ошибки доступны по ссылке. Не печатайте context в публичном сообщении: там может оказаться часть секретного URL.
Следующий шаг: решите заранее, допустимы ли относительные ссылки и автоматические исправления. Не заменяйте null исходной непроверенной строкой, если дальше ожидается проверенный объект.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::__construct.
Uri\WhatWg\Url::resolveВосстановить полную ссылку из относительной записи HTML или конфигурации.
resolve(string $uri, &$softErrors = null): staticВосстановить полную ссылку из относительной записи HTML или конфигурации.
Запись resolve(string $uri, &$softErrors = null): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $uri | Относительный или абсолютный адрес; текущий объект — база. |
| Результат | Новый объект, исходный не меняется. |
| Отказ | Uri\WhatWg\InvalidUrlException; диагностика по контракту конструктора. |
- База https://shop.example/help/start
- resolve("delivery")
- Заменить start на delivery
- https://shop.example/help/delivery
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$page = new Url('https://shop.example/help/start');
$delivery = $page->resolve('delivery');
echo $delivery->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/help/delivery
Текущий объект становится базой, resolve получает только относительную строку. delivery заменяет start; существующий $page не изменяется.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$base = new Url('https://shop.example/help/');
foreach (['delivery', '/catalog', '//cdn.example/image.png'] as $text) {
echo $base->resolve($text)->toAsciiString(), "\n";
}
Ожидаемый результат
https://shop.example/help/delivery
https://shop.example/catalog
https://cdn.example/image.png
Ссылка /catalog начинается от корня узла; //cdn.example наследует только схему и заменяет host. Поэтому «разрешить относительно доверенной базы» не ограничивает результат тем же доменом.
Вторая необязательная &$softErrors работает как третий аргумент конструктора: успешный результат со списком исправлений, а при исключении диагностика находится в errors самого исключения. Opaque-база mailto не умеет разрешать обычный дочерний путь; fragment-ссылка имеет отдельные правила WHATWG.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::__construct, Uri\WhatWg\Url::withPath.
Uri\WhatWg\Url::equalsУбрать дубликаты ссылок по явно выбранному правилу fragment.
equals(Uri\WhatWg\Url $url, Uri\UriComparisonMode $comparisonMode = Uri\UriComparisonMode::ExcludeFragment): boolУбрать дубликаты ссылок по явно выбранному правилу fragment.
Запись equals(Uri\WhatWg\Url $url, Uri\UriComparisonMode $comparisonMode = Uri\UriComparisonMode::ExcludeFragment): bool описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Первый аргумент | Объект того же класса; другой класс — TypeError. |
| $comparisonMode | IncludeFragment или ExcludeFragment (по умолчанию), не строка и не bool. |
| Результат | bool; совпадение нормализованных сериализаций по выбранному режиму. |
| Ограничение | Не гарантирует одинаковый ресурс/ответ, владение доменом или безопасное соединение. |
- help#payment и help#delivery
- Режим по умолчанию исключает fragment
- true → Один ресурс
- IncludeFragment → false
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$a = new Url('https://shop.example/help#payment');
$b = new Url('https://shop.example/help#delivery');
echo $a->equals($b) ? "Один ресурс\n" : "Разные адреса\n";
echo $a->equals($b, \Uri\UriComparisonMode::IncludeFragment) ? "Один якорь\n" : "Разные якоря\n";
Ожидаемый результат
Один ресурс
Разные якоря
По умолчанию ExcludeFragment игнорирует fragment. IncludeFragment учитывает его. Это выбранный алгоритм эквивалентности строк, не доказательство одинакового ответа сервера.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$a = new Url('https://shop.example:443/');
$b = new Url('https://shop.example/');
echo $a->equals($b) ? "эквивалентны\n" : "различаются\n";
Ожидаемый результат
эквивалентны
WHATWG удаляет стандартный порт; RFC в этом API сохраняет его. Выбирайте один парсер для формирования и сравнения ключей, не чередуйте правила между проверкой и использованием.
equals сравнивает нормализованные сериализации выбранного класса, с учётом режима fragment. Это не === (тождество объектов), не DNS, не сравнение содержимого сайта. Порядок query-пар не сортируется; HTTP-сервер может придавать ему значение.
Перед первым запуском: как устроен вызов и что он проверяет.
Uri\WhatWg\Url::toAsciiStringПодготовить разобранный адрес к хранению или отображению в нужной форме.
toAsciiString(): stringПодготовить разобранный адрес к хранению или отображению в нужной форме.
Запись toAsciiString(): string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет. |
| Результат | string; готовая WHATWG-сериализация, домен в ASCII/IDNA. |
| Отказ | При внутренней невозможности RFC-сериализации возможен Uri\UriError; тип/число аргументов проверяет PHP. Обычная валидная строка возвращается без сети. |
- https://пример.рф/price%20list?q=%7e
- toAsciiString() выбирает форму
- https://xn--e1afmkfd.xn--p1ai/price%20list?q=%7e
- Перед HTML отдельное экранирование
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://пример.рф/price%20list?q=%7e');
echo $u->toAsciiString(), "\n";
Ожидаемый результат
https://xn--e1afmkfd.xn--p1ai/price%20list?q=%7e
Меняется форма домена IDNA/Unicode. Пробел в пути остаётся %20, а query не подвергается полному URL-decoding. Название Unicode не обещает раскодирования всех компонентов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/catalog?size=M&color=blue');
echo htmlspecialchars($u->toAsciiString(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), "\n";
Ожидаемый результат
https://shop.example/catalog?size=M&color=blue
Сначала получаем URL-строку, затем отдельно экранируем для HTML. Этот CLI вывод показывает преобразованный текст; HTTP-ответ здесь не отправляется. В JSON нужен json_encode, для каждого контекста — свой encoder.
У этих классов нет __toString: используйте явный метод вместо echo $u или (string) $u. Полученная строка может содержать credentials, query-токены и персональные данные. Полный var_dump/журнал допустим только на фиктивных тестовых адресах.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toUnicodeString.
Uri\WhatWg\Url::toUnicodeStringПодготовить разобранный адрес к хранению или отображению в нужной форме.
toUnicodeString(): stringПодготовить разобранный адрес к хранению или отображению в нужной форме.
Запись toUnicodeString(): string описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет. |
| Результат | string; готовая WHATWG-сериализация, домен в Unicode, когда применимо. |
| Отказ | При внутренней невозможности RFC-сериализации возможен Uri\UriError; тип/число аргументов проверяет PHP. Обычная валидная строка возвращается без сети. |
- https://пример.рф/price%20list?q=%7e
- toUnicodeString() выбирает форму
- https://пример.рф/price%20list?q=%7e
- Перед HTML отдельное экранирование
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://пример.рф/price%20list?q=%7e');
echo $u->toUnicodeString(), "\n";
Ожидаемый результат
https://пример.рф/price%20list?q=%7e
Меняется форма домена IDNA/Unicode. Пробел в пути остаётся %20, а query не подвергается полному URL-decoding. Название Unicode не обещает раскодирования всех компонентов.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/catalog?size=M&color=blue');
echo htmlspecialchars($u->toUnicodeString(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), "\n";
Ожидаемый результат
https://shop.example/catalog?size=M&color=blue
Сначала получаем URL-строку, затем отдельно экранируем для HTML. Этот CLI вывод показывает преобразованный текст; HTTP-ответ здесь не отправляется. В JSON нужен json_encode, для каждого контекста — свой encoder.
У этих классов нет __toString: используйте явный метод вместо echo $u или (string) $u. Полученная строка может содержать credentials, query-токены и персональные данные. Полный var_dump/журнал допустим только на фиктивных тестовых адресах.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::__debugInfoРазобрать локальную ошибку, не отдавая полный URI пользователю.
__debugInfo(): arrayРазобрать локальную ошибку, не отдавая полный URI пользователю.
Запись __debugInfo(): array описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет. |
| Результат | array с состоянием для диагностики. PHP 8.5.11 показывает scheme, username, password, host, port, path, query, fragment; это не публичные writable properties. |
| Ограничение | Формат диагностики не используйте как долговременный формат обмена. Наличие SensitiveParameter у setter не скрывает debugInfo. |
- Объект https://shop.example:8443/p
- __debugInfo() → массив
- $state["host"] → shop.example
- $state["port"] → 8443
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example:8443/p?q=1#top');
$state = $u->__debugInfo();
echo $state['host'], "\n", $state['port'], "\n";
Ожидаемый результат
shop.example
8443
__debugInfo возвращает массив, который использует var_dump. Прямой вызов показан ради договора; для бизнес-логики предпочтительнее getter.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://anna:demo@shop.example/p?token=demo');
$state = $u->__debugInfo();
echo $state['scheme'], '://', $state['host'], "\n";
echo array_key_exists('password', $state) ? "Полный dump содержит поле пароля\n" : "Нет поля\n";
Ожидаемый результат
https://shop.example
Полный dump содержит поле пароля
Только scheme и host попали в сообщение. Даже путь может содержать токен, поэтому нет универсальной функции «безопасно логировать весь URL». demo — фиктивные значения.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getScheme.
Uri\WhatWg\Url::__serializeПоддержать локальное сохранение объекта стандартным механизмом PHP.
__serialize(): arrayПоддержать локальное сохранение объекта стандартным механизмом PHP.
Запись __serialize(): array описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| Аргументы | Нет. |
| Результат | array состояния, который serialize() затем кодирует в PHP-строку. |
| PHP 8.5.11 | Внутренний формат [["uri" => строка], []]: RFC сохраняет raw-сериализацию, WHATWG — ASCII. Не редактируйте формат вручную и не обещайте его стабильность другим реализациям. |
| Ограничение | Credentials и query могут попасть в состояние. Нет шифрования или подписи; сеть не используется. |
- https://shop.example/help
- __serialize()
- Массив для механизма PHP
- Исходный объект прежний
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/help');
$data = $u->__serialize();
echo is_array($data) ? "Массив состояния\n" : "Другой тип\n";
echo $u->toAsciiString(), "\n";
Ожидаемый результат
Массив состояния
https://shop.example/help
__serialize возвращает array, не текст serialize(). Упоминание string в Return Values текущей страницы PHP.net противоречит её же сигнатуре; здесь используется подтверждённый тип PHP 8.5.11.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/help#delivery');
$bytes = serialize($u);
$copy = unserialize($bytes, ['allowed_classes' => [Url::class]]);
echo $copy instanceof Url && $copy->equals($u, \Uri\UriComparisonMode::IncludeFragment) ? "Адрес восстановлен\n" : "Ошибка\n";
echo $copy === $u ? "Тот же объект\n" : "Другой объект\n";
Ожидаемый результат
Адрес восстановлен
Другой объект
Обе стороны контролируем мы, ввод не внешний. allowed_classes не делает недоверенную PHP-сериализацию безопасной. Для внешнего обмена удобнее строка URL в JSON и повторный parse с вашей политикой.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::__unserialize.
Uri\WhatWg\Url::__unserializeПонять восстановление состояния и корректно обработать его отказ.
__unserialize(array $data): voidПонять восстановление состояния и корректно обработать его отказ.
Запись __unserialize(array $data): void описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $data | Массив, подготовленный __serialize для этого класса. Не JSON и не готовая строка serialize(). |
| Результат | void; заполняет только ещё не инициализированный объект в механизме восстановления. |
| Отказ PHP 8.5.11 | Неверная структура/строка URI → Exception; повторная инициализация → Exception с предыдущим Error. Уточнение по исходнику и отдельному запуску, не замена всех ошибок Uri на Exception. |
- Свой объект → serialize
- PHP-строка только в памяти
- unserialize создаёт новый объект
- __unserialize восстанавливает → getter
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$saved = serialize(new Url('https://shop.example/help'));
$restored = unserialize($saved, ['allowed_classes' => [Url::class]]);
if ($restored instanceof Url) {
echo $restored->toAsciiString(), "\n";
}
Ожидаемый результат
https://shop.example/help
unserialize создаёт ещё не инициализированный объект и передаёт его состояние в __unserialize. Прикладной код не должен повторно инициализировать уже готовый readonly объект.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/help');
try {
$u->__unserialize($u->__serialize());
} catch (Throwable $e) {
echo $e::class, "\n";
echo $e->getPrevious() instanceof Error ? "Предыдущая причина: Error\n" : "Другая причина\n";
}
echo $u->toAsciiString(), "\n";
Ожидаемый результат
Exception
Предыдущая причина: Error
https://shop.example/help
Точно наблюдаемая PHP 8.5.11 цепочка: внешний Exception, предыдущий Error о readonly. Поэтому catch(Error) один не ловит этот конкретный вызов. Вызываем Throwable только на узкой границе учебной диагностики, не подавляем все ошибки приложения.
Текущая страница PHP.net описывает Error для повторного вызова и InvalidUriException для неправильного URI. Tagged PHP 8.5.11 в этом hook оборачивает readonly Error в Exception, а неправильную структуру/URI отклоняет обычным Exception. Это версионное расхождение явно отделено от правил parse/конструктора. При неверном типе $data возникает TypeError. Не собирайте сериализованные payload из внешнего ввода.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::__serialize, Uri\WhatWg\Url::parse.
Uri\Rfc3986\Uri::withSchemeПеревести подготовленную ссылку каталога на другую схему.
withScheme(?string $scheme): staticПеревести подготовленную ссылку каталога на другую схему.
Запись withScheme(?string $scheme): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $scheme | Схема без :. null удаляет схему, пустая строка недопустима. RFC проверяет синтаксис, но не смысл конкретного протокола: mailto://host/path возможен как общий URI. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- http://shop.example/p
- withScheme('https')
- https://shop.example/p
- $original по-прежнему http://shop.example/p
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$original = new Uri('http://shop.example/p');
$changed = $original->withScheme('https');
echo $changed->toString(), "\n";
echo $original->toString(), "\n";
Ожидаемый результат
https://shop.example/p
http://shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/p');
echo $u->withScheme(null)->toString(), "\n";
try { $u->withScheme(''); } catch (\Uri\InvalidUriException $e) { echo "Пустая схема отклонена\n"; }
Ожидаемый результат
//shop.example/p
Пустая схема отклонена
null — удаление компонента; это не то же, что неверная пустая схема. Получится относительная network-path ссылка, которую нельзя автоматически считать абсолютной HTTPS-ссылкой.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getScheme, Uri\Rfc3986\Uri::toString.
Uri\Rfc3986\Uri::withHostПодготовить ссылку на зеркальный каталог.
withHost(?string $host): staticПодготовить ссылку на зеркальный каталог.
Запись withHost(?string $host): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $host | Имя узла или IPv4/IPv6 со скобками, без порта и пути; Unicode-домен предварительно требует ASCII/IDNA либо WHATWG. null удаляет host, "" создаёт пустой host. Удаление host при наличии port/userinfo запрещено. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/p
- withHost('cdn.example')
- https://cdn.example/p
- $original по-прежнему https://shop.example/p
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$original = new Uri('https://shop.example/p');
$changed = $original->withHost('cdn.example');
echo $changed->toString(), "\n";
echo $original->toString(), "\n";
Ожидаемый результат
https://cdn.example/p
https://shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example:8443/p');
try { $u->withHost(null); } catch (\Uri\InvalidUriException $e) { echo "Сначала удалите порт\n"; }
echo $u->withPort(null)->withHost(null)->toString(), "\n";
Ожидаемый результат
Сначала удалите порт
https:/p
Компоненты связаны. Сохранили схему и путь, удалили authority; результат допустим как общий URI, но непригоден как обычная ссылка магазина. Политику HTTP нужно проверять отдельно.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getHost, Uri\Rfc3986\Uri::toString.
Uri\Rfc3986\Uri::withPathПолучить ссылку на файл прайс-листа, не меняя host.
withPath(string $path): staticПолучить ссылку на файл прайс-листа, не меняя host.
Запись withPath(string $path): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $path | Полный путь без query/fragment. Пустая строка допустима; null не соответствует типу. RFC не кодирует пробел автоматически: передавайте корректный path и кодируйте отдельные динамические сегменты. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/old
- withPath('/price%20list.csv')
- https://shop.example/price%20list.csv
- $original по-прежнему https://shop.example/old
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$original = new Uri('https://shop.example/old');
$changed = $original->withPath('/price%20list.csv');
echo $changed->toString(), "\n";
echo $original->toString(), "\n";
Ожидаемый результат
https://shop.example/price%20list.csv
https://shop.example/old
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/old');
$file = 'drill set';
echo $u->withPath('/items/' . rawurlencode($file))->toString(), "\n";
try { $u->withPath('/one two'); } catch (\Uri\InvalidUriException $e) { echo "Пробел нужно кодировать\n"; }
Ожидаемый результат
https://shop.example/items/drill%20set
Пробел нужно кодировать
rawurlencode обрабатывает один сегмент, а / оставлен разделителем. Не кодируйте весь URL: тогда исчезнет смысл :/?; не допускайте . и .. как пользовательские сегменты без отдельного правила.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getPath, Uri\Rfc3986\Uri::toString.
Uri\Rfc3986\Uri::withPortНастроить адрес тестового сервиса на отдельный порт.
withPort(?int $port): staticНастроить адрес тестового сервиса на отдельный порт.
Запись withPort(?int $port): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $port | Неотрицательный int либо null для удаления. RFC в PHP 8.5.11 не ограничивается 65535; для реального сетевого соединения приложение должно проверять диапазон. Без host установить порт нельзя. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/p
- withPort(8443)
- https://shop.example:8443/p
- $original по-прежнему https://shop.example/p
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$original = new Uri('https://shop.example/p');
$changed = $original->withPort(8443);
echo $changed->toString(), "\n";
echo $original->toString(), "\n";
Ожидаемый результат
https://shop.example:8443/p
https://shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example:8443/p');
echo $u->withPort(null)->toString(), "\n";
try { $u->withPort(-1); } catch (\Uri\InvalidUriException $e) { echo "Отрицательный порт отклонён\n"; }
Ожидаемый результат
https://shop.example/p
Отрицательный порт отклонён
null убирает :, а отрицательное число не является корректным текстом RFC-порта. Стандартный 443 автоматически не удаляется: это отличие от WHATWG.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getPort, Uri\Rfc3986\Uri::toString.
Uri\Rfc3986\Uri::withQueryПостроить ссылку на отфильтрованный каталог.
withQuery(?string $query): staticПостроить ссылку на отфильтрованный каталог.
Запись withQuery(?string $query): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $query | Query-компонент без первого ?. null удаляет ?, "" оставляет явный пустой ?. Пары параметров метод не собирает, HTML-экранирование не делает. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/catalog
- withQuery('category=tools&page=2')
- https://shop.example/catalog?category=tools&page=2
- $original по-прежнему https://shop.example/catalog
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$original = new Uri('https://shop.example/catalog');
$changed = $original->withQuery('category=tools&page=2');
echo $changed->toString(), "\n";
echo $original->toString(), "\n";
Ожидаемый результат
https://shop.example/catalog?category=tools&page=2
https://shop.example/catalog
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/find?q=old');
$query = http_build_query(['q' => 'drill set', 'page' => 2], '', '&', PHP_QUERY_RFC3986);
echo $u->withQuery($query)->toString(), "\n";
echo $u->withQuery('')->toString(), "\n";
echo $u->withQuery(null)->toString(), "\n";
Ожидаемый результат
https://shop.example/find?q=drill%20set&page=2
https://shop.example/find?
https://shop.example/find
http_build_query преобразует массив именованных значений в query. PHP_QUERY_RFC3986 использует %20; это отдельно от структуры Uri. Для повторных одинаковых ключей выберите явный формат протокола, не потеряйте их при преобразовании в массив.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getQuery, Uri\Rfc3986\Uri::toString.
Uri\Rfc3986\Uri::withFragmentДать ссылку на раздел доставки внутри справки.
withFragment(?string $fragment): staticДать ссылку на раздел доставки внутри справки.
Запись withFragment(?string $fragment): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $fragment | Имя fragment без #, в корректном RFC-кодировании. null удаляет #; "" оставляет явный пустой fragment. Метод не проверяет HTML id. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/help
- withFragment('delivery')
- https://shop.example/help#delivery
- $original по-прежнему https://shop.example/help
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$original = new Uri('https://shop.example/help');
$changed = $original->withFragment('delivery');
echo $changed->toString(), "\n";
echo $original->toString(), "\n";
Ожидаемый результат
https://shop.example/help#delivery
https://shop.example/help
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$u = new Uri('https://shop.example/help#delivery');
echo $u->withFragment('')->toString(), "\n";
echo $u->withFragment(null)->toString(), "\n";
try { $u->withFragment('#payment'); } catch (\Uri\InvalidUriException $e) { echo "Передайте имя без решётки\n"; }
Ожидаемый результат
https://shop.example/help#
https://shop.example/help
Передайте имя без решётки
В отличие от WHATWG setter, RFC не снимает начальную # за вас. Видимый fragment — часть клиентской навигации, а не команда серверу.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getFragment, Uri\Rfc3986\Uri::toString.
Uri\Rfc3986\Uri::withUserInfoУбрать встроенные учётные данные из учебной ссылки.
withUserInfo(#[\SensitiveParameter] ?string $userinfo): staticУбрать встроенные учётные данные из учебной ссылки.
Запись withUserInfo(#[\SensitiveParameter] ?string $userinfo): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $userinfo | Весь блок username[:password] без @, либо null для удаления. #[SensitiveParameter] скрывает этот аргумент в поддерживаемой трассировке, но не строку объекта, debugInfo или журнал. Пустая строка оставляет @; нужен host. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://anna:demo@shop.example/p
- withUserInfo(null)
- https://shop.example/p
- $original по-прежнему https://anna:demo@shop.example/p
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$original = new Uri('https://anna:demo@shop.example/p');
$changed = $original->withUserInfo(null);
echo $changed->toString(), "\n";
echo $original->toString(), "\n";
Ожидаемый результат
https://shop.example/p
https://anna:demo@shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\Rfc3986\Uri as Uri;
$base = new Uri('https://shop.example/p');
$testName = 'team:west';
$u = $base->withUserInfo(rawurlencode($testName) . ':demo');
echo $u->getUsername(), "\n";
echo $u->withUserInfo(null)->toString(), "\n";
Ожидаемый результат
team%3Awest
https://shop.example/p
Кодируем отдельное имя, чтобы двоеточие в нём не стало разделителем пароля. demo — фиктивное значение. В современных HTTP-приложениях credentials в URL обычно следует отклонять, а не строить автоматически.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\Rfc3986\Uri::getUserInfo, Uri\Rfc3986\Uri::toString.
Uri\WhatWg\Url::withSchemeОбновить браузерную ссылку с HTTP на HTTPS.
withScheme(string $scheme): staticОбновить браузерную ссылку с HTTP на HTTPS.
Запись withScheme(string $scheme): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $scheme | Новая схема; WHATWG принимает также запись с :. null не разрешён. Смена special↔non-special и некоторые переходы file не выполняются: метод может вернуть новый объект со старым значением без исключения. Проверяйте результат. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- http://shop.example/p
- withScheme('https')
- https://shop.example/p
- $original по-прежнему http://shop.example/p
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$original = new Url('http://shop.example/p');
$changed = $original->withScheme('https');
echo $changed->toAsciiString(), "\n";
echo $original->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/p
http://shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/p');
$changed = $u->withScheme('mailto');
echo $changed->getScheme(), "\n";
echo $changed === $u ? "Тот же объект\n" : "Новый объект без такой смены\n";
Ожидаемый результат
https
Новый объект без такой смены
Как browser protocol setter, операция не превращает hierarchical HTTPS URL в mailto. Создайте новый Url для другого рода адреса. Успешный вызов не гарантирует, что желаемое значение применено.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getScheme, Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::withHostЗаменить домен каталога на международное имя.
withHost(?string $host): staticЗаменить домен каталога на международное имя.
Запись withHost(?string $host): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $host | Hostname, не authority: не передавайте порт, userinfo, путь. Unicode-домен проходит IDNA. null/"" не могут убрать обязательный host HTTPS; неприменимые операции с opaque URL могут ничего не менять. Setter может остановиться на разделителе. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/p
- withHost('пример.рф')
- https://xn--e1afmkfd.xn--p1ai/p
- $original по-прежнему https://shop.example/p
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$original = new Url('https://shop.example/p');
$changed = $original->withHost('пример.рф');
echo $changed->toAsciiString(), "\n";
echo $original->toAsciiString(), "\n";
Ожидаемый результат
https://xn--e1afmkfd.xn--p1ai/p
https://shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/p');
echo $u->withHost('cdn.example/ignored')->toAsciiString(), "\n";
try { $u->withHost(null); } catch (\Uri\WhatWg\InvalidUrlException $e) { echo "HTTPS требует host\n"; }
Ожидаемый результат
https://cdn.example/p
HTTPS требует host
Суффикс /ignored не становится путём и не гарантированно отклоняется. Для поля «имя домена» приложение должно задавать отдельный строгий договор; withHost не является валидатором всей введённой строки.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getAsciiHost, Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::withPathСобрать ссылку на документ с пробелом в имени.
withPath(string $path): staticСобрать ссылку на документ с пробелом в имени.
Запись withPath(string $path): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $path | Pathname; WHATWG кодирует необходимые символы, нормализует dot-segments. Для HTTP(S) относительная запись превращается в путь от корня. Opaque path (mailto и др.) не переписывается как иерархический. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/old
- withPath('/price list.csv')
- https://shop.example/price%20list.csv
- $original по-прежнему https://shop.example/old
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$original = new Url('https://shop.example/old');
$changed = $original->withPath('/price list.csv');
echo $changed->toAsciiString(), "\n";
echo $original->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/price%20list.csv
https://shop.example/old
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/old');
echo $u->withPath('/items/a?b#c')->toAsciiString(), "\n";
echo $u->withPath('/a/%2e%2e/b')->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/items/a%3Fb%23c
https://shop.example/b
? и # внутри setter-значения становятся данными пути, не query/fragment. Кодированный .. считается навигационным сегментом: percent-кодирование само по себе не защищает от изменения маршрута.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getPath, Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::withPortВыбрать порт отдельного учебного HTTPS-сервиса.
withPort(?int $port): staticВыбрать порт отдельного учебного HTTPS-сервиса.
Запись withPort(?int $port): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $port | int 0…65535 или null. Стандартный порт схемы убирается; file/URL без host не получает обычный сетевой порт. Неприменимая операция может не менять значение. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/p
- withPort(8443)
- https://shop.example:8443/p
- $original по-прежнему https://shop.example/p
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$original = new Url('https://shop.example/p');
$changed = $original->withPort(8443);
echo $changed->toAsciiString(), "\n";
echo $original->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example:8443/p
https://shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example:8443/p');
echo $u->withPort(443)->toAsciiString(), "\n";
try { $u->withPort(70000); } catch (\Uri\WhatWg\InvalidUrlException $e) { echo "Порт вне диапазона\n"; }
Ожидаемый результат
https://shop.example/p
Порт вне диапазона
В WHATWG 443 для https — стандартный и не хранится как явный порт. Не используйте это правило для RFC Uri: там 70000 может быть синтаксически представимым.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getPort, Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::withQueryДобавить фильтр к готовой ссылке каталога.
withQuery(?string $query): staticДобавить фильтр к готовой ссылке каталога.
Запись withQuery(?string $query): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $query | Строка search: допускается начальный ?, который снимается. null и "" удаляют query; "?" оставляет пустой query. WHATWG кодирует нужные символы, но не разделяет значения параметров за вас. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/catalog
- withQuery('category=tools&page=2')
- https://shop.example/catalog?category=tools&page=2
- $original по-прежнему https://shop.example/catalog
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$original = new Url('https://shop.example/catalog');
$changed = $original->withQuery('category=tools&page=2');
echo $changed->toAsciiString(), "\n";
echo $original->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/catalog?category=tools&page=2
https://shop.example/catalog
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/find?q=old');
$query = http_build_query(['q' => 'drill set'], '', '&', PHP_QUERY_RFC3986);
echo $u->withQuery($query)->toAsciiString(), "\n";
echo $u->withQuery('')->toAsciiString(), "\n";
echo $u->withQuery('?')->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/find?q=drill%20set
https://shop.example/find
https://shop.example/find?
Пустая строка здесь удаляет компонент, что отличается от RFC withQuery(""). http_build_query задаёт границы данных: простой "q=" . $input позволил бы & в input добавить другой параметр.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getQuery, Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::withFragmentСоздать ссылку на конкретную часть справки.
withFragment(?string $fragment): staticСоздать ссылку на конкретную часть справки.
Запись withFragment(?string $fragment): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $fragment | Fragment, допускается начальная #. null и "" удаляют fragment, "#" задаёт явно пустой. Нужные символы кодируются по WHATWG; HTML id не проверяется. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://shop.example/help
- withFragment('delivery')
- https://shop.example/help#delivery
- $original по-прежнему https://shop.example/help
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$original = new Url('https://shop.example/help');
$changed = $original->withFragment('delivery');
echo $changed->toAsciiString(), "\n";
echo $original->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/help#delivery
https://shop.example/help
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/help#delivery');
echo $u->withFragment('part two')->toAsciiString(), "\n";
echo $u->withFragment('')->toAsciiString(), "\n";
echo $u->withFragment('#')->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/help#part%20two
https://shop.example/help
https://shop.example/help#
Три разных намерения: имя с пробелом, отсутствие fragment, пустой fragment. Это не установка DOM-фокуса и не проверка наличия раздела: UI выполняет их отдельно.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getFragment, Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::withUsernameУдалить имя из ссылки со встроенными credentials.
withUsername(?string $username): staticУдалить имя из ссылки со встроенными credentials.
Запись withUsername(?string $username): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $username | Отдельное имя: символы-разделители кодируются. null и "" очищают имя, но не пароль. Наличие password может сохранить пустой username; URL без host/file не поддерживает обычные credentials. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://anna@shop.example/p
- withUsername(null)
- https://shop.example/p
- $original по-прежнему https://anna@shop.example/p
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$original = new Url('https://anna@shop.example/p');
$changed = $original->withUsername(null);
echo $changed->toAsciiString(), "\n";
echo $original->toAsciiString(), "\n";
Ожидаемый результат
https://shop.example/p
https://anna@shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://shop.example/p');
$withName = $u->withUsername('team:west');
echo $withName->getUsername(), "\n";
echo $withName->withUsername(null)->toAsciiString(), "\n";
Ожидаемый результат
team%3Awest
https://shop.example/p
Передаём отдельное имя, не user:password. WHATWG кодирует двоеточие как данные имени. Для полного удаления credentials требуется также withPassword(null).
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getUsername, Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\Url::withPasswordУдалить пароль из фиктивной учебной ссылки.
withPassword(#[\SensitiveParameter] ?string $password): staticУдалить пароль из фиктивной учебной ссылки.
Запись withPassword(#[\SensitiveParameter] ?string $password): static описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $password | Отдельный пароль: специальные символы кодируются; null/"" очищают только пароль. #[SensitiveParameter] относится к аргументу, но не к возвращённому объекту/его сериализации. URL без подходящего host не поддерживает credentials. |
| Результат | Новый объект того же final readonly класса; исходный не изменён. Для WHATWG новое значение может нормализоваться или не примениться по правилам URL setter. |
| Отказ | Некорректный компонент или недопустимое сочетание компонентов: Uri\InvalidUriException (у WHATWG — подкласс Uri\WhatWg\InvalidUrlException с errors). Неверный PHP-тип: TypeError при strict_types=1. Исходный объект не меняется даже при отказе. |
| Версия | PHP 8.5.0; для 8.2–8.4 эти классы недоступны. |
- https://anna:demo@shop.example/p
- withPassword(null)
- https://anna@shop.example/p
- $original по-прежнему https://anna:demo@shop.example/p
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$original = new Url('https://anna:demo@shop.example/p');
$changed = $original->withPassword(null);
echo $changed->toAsciiString(), "\n";
echo $original->toAsciiString(), "\n";
Ожидаемый результат
https://anna@shop.example/p
https://anna:demo@shop.example/p
with-метод создаёт новый объект. = сохраняет его в $changed; $original остаётся прежним. Если вызвать метод и отбросить возвращённое значение, дальнейшая работа со старой переменной не увидит изменения.
Теперь изменим требование и проверим конкретную границу этого компонента. try выполняет попытку, catch перехватывает только указанный отказ; создание/изменение строки не отправляет запрос.
<?php
declare(strict_types=1);
use Uri\WhatWg\Url as Url;
$u = new Url('https://anna@shop.example/p');
$test = $u->withPassword('demo:west');
echo $test->getPassword(), "\n";
echo $test->withUsername(null)->withPassword(null)->toAsciiString(), "\n";
Ожидаемый результат
demo%3Awest
https://shop.example/p
Только фиктивный demo:west допустим для вывода в уроке. В реальном интерфейсе не печатайте пароль. Смена username не очищает password, поэтому очищены оба поля.
Операция заменяет этот компонент, не обещая бизнес-корректность всего адреса. После сборки проверьте схему, host, port, credentials и нужные ограничения пути. Измеряйте стоимость на своих данных: последовательные with-вызовы создают отдельные объекты; не нужна цепочка ради самой цепочки.
Практика: предскажите результат повторного вызова с тем же аргументом и сравните строки. Даже если строки совпали, новый объект и исходный объект не обязаны быть тождественны по ===.
Перед первым запуском: как устроен вызов и что он проверяет.
Следующий шаг: Uri\WhatWg\Url::getPassword, Uri\WhatWg\Url::toAsciiString.
Uri\WhatWg\UrlValidationError::__constructСоздать запись для адаптера или теста отображения ошибок.
__construct(string $context, Uri\WhatWg\UrlValidationErrorType $type, bool $failure)Создать запись для адаптера или теста отображения ошибок.
Запись __construct(string $context, Uri\WhatWg\UrlValidationErrorType $type, bool $failure) описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $context | Строка контекста; может быть чувствительной, не обещан номер позиции. |
| $type | Конкретный case pure enum, не его строковое имя. |
| $failure | bool; true = отказ, false = мягкая диагностика. |
| Результат | new возвращает final readonly объект с public context/type/failure. |
| Ошибка | Неверные PHP-типы — TypeError в strict mode. Соответствие context/type не вычисляется; это контейнер, не валидатор. |
- :70000 + PortOutOfRange + true
- new UrlValidationError
- type и failure доступны через ->
- Нужна правка
<?php
declare(strict_types=1);
$item = new Uri\WhatWg\UrlValidationError(
':70000', Uri\WhatWg\UrlValidationErrorType::PortOutOfRange, true
);
echo $item->type->name, "\n";
echo $item->failure ? "Нужна правка\n" : "Можно рассмотреть исправление\n";
Ожидаемый результат
PortOutOfRange
Нужна правка
new создаёт диагностические данные. Этот вызов не анализирует :70000, а принимает уже выбранные type/failure. Для настоящего разбора нужен Url::parse.
<?php
declare(strict_types=1);
$item = new Uri\WhatWg\UrlValidationError('sample', Uri\WhatWg\UrlValidationErrorType::InvalidUrlUnit, false);
try { $item->failure = true; } catch (Error $e) { echo "Запись readonly\n"; }
echo $item->failure ? "hard\n" : "soft\n";
Ожидаемый результат
Запись readonly
soft
Тип final readonly исключает наследование и изменение полей после инициализации. Если тесту нужен другой результат, создайте другую запись; не переписывайте историю ошибки.
Перед первым запуском: как устроен вызов и что он проверяет.
Uri\WhatWg\InvalidUrlException::__constructПередать отказ URL вместе со структурированной причиной вызывающему коду.
__construct(string $message = "", array $errors = [], int $code = 0, ?Throwable $previous = null)Передать отказ URL вместе со структурированной причиной вызывающему коду.
Запись __construct(string $message = "", array $errors = [], int $code = 0, ?Throwable $previous = null) описывает договор PHP 8.5+. $ перед именем обозначает параметр, ? перед типом допускает null; null означает отсутствие, а пустая строка — отдельное значение. static в возвращаемом типе означает объект того же класса. Вызов без аргументов всё равно заканчивается скобками ().
| Часть договора | Смысл |
|---|---|
| $message | Человеческое описание, по умолчанию ""; не включайте секретный адрес без необходимости. |
| $errors | Список UrlValidationError, по умолчанию []. Public readonly array после создания. Значения-ссылки и не-list/не-объекты отклоняются ValueError в PHP 8.5.11. |
| $code | Целое число приложения, default0; не обязан быть HTTP-статусом. |
| $previous | Предыдущая причина Throwable либо null. |
| Результат/ошибки | new возвращает исключение; не бросает его сам. TypeError для неверного типа; ValueError для неправильного списка; readonly-защита при повторной инициализации errors. |
- Готовая запись PortOutOfRange
- [$item] → errors
- message и code17 → Exception
- Доступны getMessage и errors[0]
<?php
declare(strict_types=1);
$item = new Uri\WhatWg\UrlValidationError(':70000', Uri\WhatWg\UrlValidationErrorType::PortOutOfRange, true);
$error = new Uri\WhatWg\InvalidUrlException('Ссылка карточки17 отклонена', [$item], 17);
echo $error->getMessage(), "\n";
echo $error->errors[0]->type->name, "\n";
Ожидаемый результат
Ссылка карточки17 отклонена
PortOutOfRange
[$item] — список с индексом0. Код17 принадлежит приложению. Объект исключения пока только создан; чтобы прервать выполнение, понадобился бы throw $error.
<?php
declare(strict_types=1);
$cause = new RuntimeException('Фиктивная причина импорта');
$error = new Uri\WhatWg\InvalidUrlException('Адрес отклонён', [], 0, $cause);
echo $error->getPrevious() === $cause ? "Причина сохранена\n" : "Нет причины\n";
try { new Uri\WhatWg\InvalidUrlException('bad', ['message']); }
catch (ValueError $e) { echo "Нужен список UrlValidationError\n"; }
Ожидаемый результат
Причина сохранена
Нужен список UrlValidationError
Массив должен быть последовательным списком диагностических объектов. Строки, произвольные ключи и ссылочные элементы не принимаются (проверено по PHP 8.5.11). Это строже простого типа array в сигнатуре.
Перед первым запуском: как устроен вызов и что он проверяет.
Источники и обсуждения
- PHP: book.uri
- PHP: class.uri-rfc3986-uri
- PHP: class.uri-whatwg-url
- PHP: enum.uri-uricomparisonmode
- PHP: class.uri-uriexception
- PHP: class.uri-urierror
- PHP: class.uri-invaliduriexception
- PHP: class.uri-whatwg-invalidurlexception
- PHP: class.uri-whatwg-urlvalidationerror
- PHP: enum.uri-whatwg-urlvalidationerrortype
- PHP 8.5: always enabled URI extension
- RFC3986: syntax, resolution and comparison
- WHATWG URL Standard
- PHP8.5.11 URI API