Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
n/a
0 / 0
n/a
0 / 0
CRAP
n/a
0 / 0
ExceptionsHelper
n/a
0 / 0
n/a
0 / 0
33
n/a
0 / 0
 sentryEventHandler
n/a
0 / 0
n/a
0 / 0
2
 getAnonymizedSentryException
n/a
0 / 0
n/a
0 / 0
2
 getAnonymizedSentryStackTrace
n/a
0 / 0
n/a
0 / 0
4
 getAnonymizedSentryParams
n/a
0 / 0
n/a
0 / 0
10
 getAnonymizedMessage
n/a
0 / 0
n/a
0 / 0
2
 getAnonymizedStackTrace
n/a
0 / 0
n/a
0 / 0
6
 handleArgs
n/a
0 / 0
n/a
0 / 0
7
1<?php
2
3namespace App\Lib\Helpers;
4
5/**
6 * @codeCoverageIgnore
7 */
8class ExceptionsHelper
9{
10    /**
11     * Handle sentry event.
12     *
13     * @param \Sentry\Event $event
14     * @return null|\Sentry\Event
15     */
16    public static function sentryEventHandler(\Sentry\Event $event): ?\Sentry\Event
17    {
18        // Remove unwanted reports.
19        $event->setModules([]);
20        $event->setOsContext(null);
21
22        /** @var \Sentry\ExceptionDataBag[] */
23        $exceptionBag = [];
24        foreach ($event->getExceptions() as $exceptionDataBag) {
25            $exceptionBag[] = self::getAnonymizedSentryException($exceptionDataBag);
26        }
27        $event->setExceptions($exceptionBag);
28        return $event;
29    }
30
31    /**
32     * Anonimize Sentry Exception bag.
33     *
34     * @param \Sentry\ExceptionDataBag $exceptionDataBag
35     * @return \Sentry\ExceptionDataBag
36     */
37    private static function getAnonymizedSentryException(
38        \Sentry\ExceptionDataBag $exceptionDataBag
39    ): \Sentry\ExceptionDataBag {
40        $exceptionDataBag->setValue(self::getAnonymizedMessage($exceptionDataBag->getValue()));
41        if ($stackTrace = $exceptionDataBag->getStacktrace()) {
42            $exceptionDataBag->setStacktrace(self::getAnonymizedSentryStackTrace($stackTrace));
43        }
44        return $exceptionDataBag;
45    }
46
47    /**
48     * Anonymise sentry stacktrace.
49     *
50     * @param \Sentry\Stacktrace $stackTrace
51     * @return \Sentry\Stacktrace
52     */
53    private static function getAnonymizedSentryStackTrace(\Sentry\Stacktrace $stackTrace): \Sentry\Stacktrace
54    {
55        /** @var \Sentry\Frame[] */
56        $frames = \array_reverse($stackTrace->getFrames(), true);
57        $i      = \count($frames) - 1;
58        // * Remove all but keep first (index.php).
59        while ($i-- > 0) {
60            $stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
61        }
62        foreach ($frames as $index => $frame) {
63            $frame->setPostContext([]);
64            $frame->setPreContext([]);
65            $frame->setContextLine('');
66            $frame->setVars(self::getAnonymizedSentryParams($frame->getVars()));
67            $stackTrace->addFrame($frame);
68            if ($index === 0) {
69                $stackTrace->removeFrame(count($stackTrace->getFrames()) - 1);
70            }
71        }
72        return $stackTrace;
73    }
74
75    /**
76     * Get anonymized sentry params.
77     *
78     * @param mixed[] $params
79     * @return string[]
80     */
81    private static function getAnonymizedSentryParams(array $params): array
82    {
83        foreach ($params as &$param) {
84            $type = \strtolower(\gettype($param));
85            if (\is_string($param)) {
86                $type = \explode(' ', \trim(strtolower($param)))[0] ?? null;
87            }
88            switch (true) {
89                case $type === 'lambda':
90                case $type === 'object':
91                    continue 2;
92                case $type === 'array' and \is_array($param):
93                    $param = sprintf('Array(%d)', \count($param));
94                    continue 2;
95                default:
96                    if (\is_numeric($param)) {
97                        $param = \ucfirst($type);
98                        continue 2;
99                    }
100                    if (\is_string($param)) {
101                        $param = sprintf('String(%d)', \strlen($param));
102                        continue 2;
103                    }
104            } //end switch
105        } //end foreach
106        return $params;
107    }
108
109    /**
110     * Anonymize at least Sql queries.
111     *
112     * @param string $message
113     * @return string
114     */
115    public static function getAnonymizedMessage(string $message): string
116    {
117        $message = \preg_replace('/^(.*)( \(SQL:.*\))$/', '$1', $message) ?? $message;
118        if ($sqlPos = \strpos($message, 'to use near')) {
119            $message = \mb_strcut($message, 0, $sqlPos);
120        }
121        return $message;
122    }
123
124    /**
125     * Anonymize stack trace
126     *
127     * @param \Throwable $e
128     * @return string
129     */
130    public static function getAnonymizedStackTrace(\Throwable $e): string
131    {
132        $projectPath   = \realpath(\sprintf('%s/../', \app_path()));
133        $traceStack    = $e->getTrace();
134        $traceStr      = '';
135        $maxStackStage = 0;
136        foreach ($traceStack as $stackStage => $stack) {
137            $maxStackStage = $stackStage;
138            $traceStr     .= \sprintf(
139                '#%s %s(%d): ',
140                $stackStage,
141                str_replace($projectPath, '', $stack['file'] ?? ''),
142                $stack['line'] ?? ''
143            );
144            if (isset($stack['class'])) {
145                $traceStr .= "{$stack['class']}->{$stack['function']}(";
146                if (isset($stack['args'])) {
147                    $traceStr .= self::handleArgs($stack);
148                }
149                $traceStr .= ")\n";
150            } elseif (isset($stack['function'])) {
151                $traceStr .= "{$stack['function']}(";
152                if (isset($stack['args'])) {
153                    $traceStr .= self::handleArgs($stack);
154                }
155                $traceStr .= ")\n";
156            }
157        } //end foreach
158        $maxStackStage++;
159        $traceStr .= "#$maxStackStage {main}";
160        return $traceStr;
161    }
162
163    /**
164     * Handle Args.
165     *
166     * @param array $stack
167     * @return string
168     */
169    public static function handleArgs(array $stack): string
170    {
171        $traceArgs = '';
172        $argsCount = count($stack['args']);
173        for ($i = 0; $i < $argsCount; $i++) {
174            $type = \gettype($stack['args'][$i]);
175            switch ($type) {
176                case 'object':
177                    $traceArgs .= sprintf('Object(%s)', \get_class($stack['args'][$i]));
178                    break;
179                case 'array':
180                    $traceArgs .= sprintf('Array(%d)', \count($stack['args'][$i]));
181                    break;
182                case 'string':
183                    $traceArgs .= sprintf('String(%d)', \strlen($stack['args'][$i]));
184                    break;
185                default:
186                    $traceArgs .= \gettype($stack['args'][$i]);
187            }
188            $traceArgs .= ($i !== ($argsCount - 1) ? ',' : '');
189        }
190        return $traceArgs;
191    }
192}