1: <?php declare(strict_types=1);
2:
3: namespace Salient\Utility\Exception;
4:
5: /**
6: * @api
7: */
8: class PcreErrorException extends AbstractUtilityException
9: {
10: private const ERROR_MESSAGE_MAP = [
11: \PREG_NO_ERROR => 'No error',
12: \PREG_INTERNAL_ERROR => 'Internal error',
13: \PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 characters, possibly incorrectly encoded',
14: \PREG_BAD_UTF8_OFFSET_ERROR => 'The offset did not correspond to the beginning of a valid UTF-8 code point',
15: \PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit exhausted',
16: \PREG_RECURSION_LIMIT_ERROR => 'Recursion limit exhausted',
17: \PREG_JIT_STACKLIMIT_ERROR => 'JIT stack limit exhausted',
18: ];
19:
20: protected int $PcreError;
21: protected string $PcreErrorMessage;
22: protected string $Function;
23: /** @var array<string,callable(array<string|null>): string>|string[]|string */
24: protected $Pattern;
25: /** @var string[]|string */
26: protected $Subject;
27: /** @var array<int,string> */
28: private static array $ErrorNameMap;
29:
30: /**
31: * @param array<string,callable(array<string|null>): string>|string[]|string $pattern
32: * @param string[]|string $subject
33: */
34: public function __construct(?int $error, string $function, $pattern, $subject)
35: {
36: $error ??= preg_last_error();
37: $message = \PHP_VERSION_ID < 80000
38: ? self::ERROR_MESSAGE_MAP[$error] ?? 'Unknown error'
39: : preg_last_error_msg();
40:
41: $this->PcreError = $error;
42: $this->PcreErrorMessage = $message;
43: $this->Function = $function;
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 ??= $this->getErrorNameMap())[$error],
51: $message,
52: ));
53: }
54:
55: /**
56: * Get the exception's PCRE error code
57: */
58: public function getPcreError(): int
59: {
60: return $this->PcreError;
61: }
62:
63: /**
64: * Get the name of the exception's PCRE error
65: */
66: public function getPcreErrorName(): string
67: {
68: return self::$ErrorNameMap[$this->PcreError];
69: }
70:
71: /**
72: * Get the exception's PCRE error message
73: */
74: public function getPcreErrorMessage(): string
75: {
76: return $this->PcreErrorMessage;
77: }
78:
79: /**
80: * Get the name of the PCRE function that failed
81: */
82: public function getFunction(): string
83: {
84: return $this->Function;
85: }
86:
87: /**
88: * Get the pattern passed to the PCRE function
89: *
90: * @return array<string,callable(array<string|null>): string>|string[]|string
91: */
92: public function getPattern()
93: {
94: return $this->Pattern;
95: }
96:
97: /**
98: * Get the subject passed to the PCRE function
99: *
100: * @return string[]|string
101: */
102: public function getSubject()
103: {
104: return $this->Subject;
105: }
106:
107: /**
108: * @return array<int,string>
109: */
110: private function getErrorNameMap(): array
111: {
112: /** @var array<string,mixed> */
113: $constants = get_defined_constants(true)['pcre'];
114: foreach ($constants as $name => $value) {
115: if (substr($name, -6) !== '_ERROR') {
116: continue;
117: }
118: /** @var int $value */
119: $errors[$value] = $name;
120: }
121:
122: return $errors ?? [];
123: }
124: }
125: