message = $message; $this->code = $code; $this->previous = $previous; // Capture backtrace; first element is this constructor call site $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); if (!empty($bt)) { $first = $bt[0]; $this->file = $first['file'] ?? 'unknown'; $this->line = $first['line'] ?? 0; } // Exclude current frame for readability $this->trace = array_slice($bt, 1); } /** * Clone the exception * Tries to clone the Exception, which results in Fatal error. * @link https://php.net/manual/en/exception.clone.php * @return void */ public function __clone(): void { // Mimic internal Exception behavior: cloning not allowed. trigger_error('Trying to clone an uncloneable object of class ' . static::class, E_USER_ERROR); } /** * String representation of the exception * @link https://php.net/manual/en/exception.tostring.php * @return string the string representation of the exception. */ public function __toString(): string { return sprintf( "%s: %s in %s:%d\nStack trace:\n%s", static::class, $this->getMessage(), $this->getFile(), $this->getLine(), $this->getTraceAsString() ); } public function __wakeup(): void { // On wakeup we don't have original trace; reset trace to empty $this->trace = []; } /** * Gets the Exception message * @link https://php.net/manual/en/exception.getmessage.php * @return string the Exception message as a string. */ final public function getMessage(): string { return $this->message; } /** * Gets the Exception code * @link https://php.net/manual/en/exception.getcode.php * @return mixed|int the exception code as integer in * Exception but possibly as other type in * Exception descendants (for example as * string in PDOException). */ final public function getCode(): int { return $this->code; } /** * Gets the file in which the exception occurred * @link https://php.net/manual/en/exception.getfile.php * @return string the filename in which the exception was created. */ final public function getFile(): string { return $this->file; } /** * Gets the line in which the exception occurred * @link https://php.net/manual/en/exception.getline.php * @return int the line number where the exception was created. */ final public function getLine(): int { return $this->line; } /** * Gets the stack trace * @link https://php.net/manual/en/exception.gettrace.php * @return array the Exception stack trace as an array. */ final public function getTrace(): array { return $this->trace; } /** * Returns previous Exception * @link https://php.net/manual/en/exception.getprevious.php * @return null|Throwable Returns the previous {@see Throwable} if available, or NULL otherwise. * or null otherwise. */ final public function getPrevious(): ?Throwable { return $this->previous; } /** * Gets the stack trace as a string * @link https://php.net/manual/en/exception.gettraceasstring.php * @return string the Exception stack trace as a string. */ final public function getTraceAsString(): string { $lines = []; foreach ($this->trace as $i => $frame) { $file = $frame['file'] ?? '[internal function]'; $line = $frame['line'] ?? 0; $func = $frame['function'] ?? ''; $class = $frame['class'] ?? ''; $type = $frame['type'] ?? ''; $lines[] = sprintf('#%d %s(%s): %s%s%s()', $i, $file, $line, $class, $type, $func); } $lines[] = sprintf('#%d {main}', count($lines)); return implode("\n", $lines); } }