| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- <?php
- /**
- * League.Uri (https://uri.thephpleague.com)
- *
- * (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- declare(strict_types=1);
- namespace League\Uri;
- use BackedEnum;
- use League\Uri\Contracts\UriComponentInterface;
- use Stringable;
- use TypeError;
- use Uri\Rfc3986\Uri as Rfc3986Uri;
- use Uri\WhatWg\Url as WhatWgUrl;
- use ValueError;
- use function array_is_list;
- use function array_map;
- use function get_debug_type;
- use function implode;
- use function is_array;
- use function is_float;
- use function is_infinite;
- use function is_nan;
- use function is_object;
- use function is_resource;
- use function is_scalar;
- use function json_encode;
- use const JSON_PRESERVE_ZERO_FRACTION;
- enum StringCoercionMode
- {
- /**
- * PHP conversion mode.
- *
- * Guarantees that only scalar values, BackedEnum, and null are accepted.
- * Any object, UnitEnum, resource, or recursive structure
- * results in an error.
- *
- * - null: is not converted and stays the `null` value
- * - string: used as-is
- * - bool: converted to string “0” (false) or “1” (true)
- * - int: converted to numeric string (123 -> “123”)
- * - float: converted to decimal string (3.14 -> “3.14”)
- * - Backed Enum: converted to their backing value and then stringify see int and string
- */
- case Native;
- /**
- * Ecmascript conversion mode.
- *
- * Guarantees that only scalar values, BackedEnum, and null are accepted.
- * Any resource, or recursive structure results in an error.
- *
- * - null: converted to string “null”
- * - string: used as-is
- * - bool: converted to string “false” (false) or “true” (true)
- * - int: converted to numeric string (123 -> “123”)
- * - float: converted to decimal string (3.14 -> “3.14”), "NaN", "-Infinity" or "Infinity"
- * - Backed Enum: converted to their backing value and then stringify see int and string
- * - Array as list are flatten into a string list using the "," character as separator
- * - Associative array, Unit Enum, any object without stringification semantics is coerced to "[object Object]".
- */
- case Ecmascript;
- private const RECURSION_MARKER = "\0__RECURSION_INTERNAL_MARKER_WHATWG__\0";
- public function isCoercible(mixed $value): bool
- {
- return self::Ecmascript === $this
- ? !is_resource($value)
- : match (true) {
- $value instanceof Rfc3986Uri,
- $value instanceof WhatWgUrl,
- $value instanceof BackedEnum,
- $value instanceof Stringable,
- is_scalar($value),
- null === $value => true,
- default => false,
- };
- }
- /**
- * @throws TypeError if the type is not supported by the specific case
- * @throws ValueError if circular reference is detected
- */
- public function coerce(mixed $value): ?string
- {
- return match ($this) {
- self::Ecmascript => match (true) {
- $value instanceof Rfc3986Uri => $value->toString(),
- $value instanceof WhatWgUrl => $value->toAsciiString(),
- $value instanceof BackedEnum => (string) $value->value,
- $value instanceof Stringable => $value->__toString(),
- is_object($value) => '[object Object]',
- is_array($value) => match (true) {
- self::hasCircularReference($value) => throw new ValueError('Recursive array structure detected; unable to coerce value.'),
- array_is_list($value) => implode(',', array_map($this->coerce(...), $value)),
- default => '[object Object]',
- },
- true === $value => 'true',
- false === $value => 'false',
- null === $value => 'null',
- is_float($value) => match (true) {
- is_nan($value) => 'NaN',
- is_infinite($value) => 0 < $value ? 'Infinity' : '-Infinity',
- default => (string) json_encode($value, JSON_PRESERVE_ZERO_FRACTION),
- },
- is_scalar($value) => (string) $value,
- default => throw new TypeError('Unable to coerce value of type "'.get_debug_type($value).'" with "'.$this->name.'" coercion.'),
- },
- self::Native => match (true) {
- $value instanceof UriComponentInterface => $value->value(),
- $value instanceof WhatWgUrl => $value->toAsciiString(),
- $value instanceof Rfc3986Uri => $value->toString(),
- $value instanceof BackedEnum => (string) $value->value,
- $value instanceof Stringable => $value->__toString(),
- false === $value => '0',
- true === $value => '1',
- null === $value => null,
- is_scalar($value) => (string) $value,
- default => throw new TypeError('Unable to coerce value of type "'.get_debug_type($value).'" with "'.$this->name.'" coercion.'),
- },
- };
- }
- /**
- * Array recursion detection.
- * @see https://stackoverflow.com/questions/9042142/detecting-infinite-array-recursion-in-php
- */
- private static function hasCircularReference(array &$arr): bool
- {
- if (isset($arr[self::RECURSION_MARKER])) {
- return true;
- }
- try {
- $arr[self::RECURSION_MARKER] = true;
- foreach ($arr as $key => &$value) {
- if (self::RECURSION_MARKER !== $key && is_array($value) && self::hasCircularReference($value)) {
- return true;
- }
- }
- return false;
- } finally {
- unset($arr[self::RECURSION_MARKER]);
- }
- }
- }
|