1: <?php declare(strict_types=1);
2:
3: namespace Salient\Utility\Exception;
4:
5: use Salient\Utility\Arr;
6: use Salient\Utility\Format;
7: use Stringable;
8:
9: /**
10: * @api
11: */
12: class PcreErrorException extends UtilityException
13: {
14: private const ERROR_MESSAGE_MAP = [
15: \PREG_NO_ERROR => 'No error',
16: \PREG_INTERNAL_ERROR => 'Internal error',
17: \PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 characters, possibly incorrectly encoded',
18: \PREG_BAD_UTF8_OFFSET_ERROR => 'The offset did not correspond to the beginning of a valid UTF-8 code point',
19: \PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit exhausted',
20: \PREG_RECURSION_LIMIT_ERROR => 'Recursion limit exhausted',
21: \PREG_JIT_STACKLIMIT_ERROR => 'JIT stack limit exhausted',
22: ];
23:
24: /** @var array<string,callable>|string[]|string */
25: protected $Pattern;
26: /** @var array<int|float|string|bool|Stringable|null>|string */
27: protected $Subject;
28: /** @var array<int,string> */
29: private static array $ErrorNameMap;
30:
31: /**
32: * @internal
33: *
34: * @param array<string,callable>|string[]|string $pattern
35: * @param array<int|float|string|bool|Stringable|null>|string $subject
36: */
37: public function __construct(?int $error, string $function, $pattern, $subject)
38: {
39: $error ??= preg_last_error();
40: $message = \PHP_VERSION_ID < 80000
41: ? self::ERROR_MESSAGE_MAP[$error] ?? 'Unknown error'
42: : preg_last_error_msg();
43:
44: $this->Pattern = $pattern;
45: $this->Subject = $subject;
46:
47: parent::__construct(sprintf(
48: 'Call to %s() failed with %s (%s)',
49: $function,
50: (self::$ErrorNameMap ??= self::getErrorNameMap())[$error] ?? "#$error",
51: $message,
52: ));
53: }
54:
55: /**
56: * @inheritDoc
57: */
58: public function __toString(): string
59: {
60: $detail = '';
61: foreach ([
62: 'Pattern' => is_array($this->Pattern)
63: ? array_map(
64: fn($value) => is_string($value) ? $value : '<callable>',
65: $this->Pattern,
66: )
67: : $this->Pattern,
68: 'Subject' => $this->Subject,
69: ] as $key => $value) {
70: if (is_array($value)) {
71: $value = Arr::isList($value)
72: ? Format::list($value)
73: : Format::array($value);
74: }
75: $detail .= sprintf("\n\n%s:\n%s", $key, rtrim($value, "\n"));
76: }
77:
78: return parent::__toString() . $detail;
79: }
80:
81: /**
82: * @return array<int,string>
83: */
84: private static function getErrorNameMap(): array
85: {
86: /** @var array<string,mixed> */
87: $constants = get_defined_constants(true)['pcre'];
88: $map = [];
89: foreach ($constants as $name => $value) {
90: if (substr($name, -6) === '_ERROR') {
91: /** @var int $value */
92: $map[$value] = $name;
93: }
94: }
95: return $map;
96: }
97: }
98: