Completed
Push — master ( 2db7cf...e69821 )
by Thomas Mauro
02:07
created

Sentry::sanitizeContextItem()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 10
cts 10
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 7
nc 8
nop 1
crap 5
1
<?php
2
3
namespace Facile\SentryModule\Log\Writer;
4
5
use Facile\SentryModule\Service\Client;
6
use Zend\Log\Writer\AbstractWriter;
7
use Zend\Log\Logger;
8
use Raven_Client;
9
10
/**
11
 * Class Sentry.
12
 */
13
class Sentry extends AbstractWriter
14
{
15
    /**
16
     * @var Client
17
     */
18
    protected $client;
19
20
    /**
21
     * @var array
22
     */
23
    protected $priorityMap = [
24
        Logger::EMERG => Raven_Client::FATAL,
25
        Logger::ALERT => Raven_Client::ERROR,
26
        Logger::CRIT => Raven_Client::ERROR,
27
        Logger::ERR => Raven_Client::ERROR,
28
        Logger::WARN => Raven_Client::WARNING,
29
        Logger::NOTICE => Raven_Client::INFO,
30
        Logger::INFO => Raven_Client::INFO,
31
        Logger::DEBUG => Raven_Client::DEBUG,
32
    ];
33
34
    /**
35
     * Sentry constructor.
36
     *
37
     * @param array $options
38
     *
39
     * @throws \RuntimeException
40
     * @throws \Zend\Log\Exception\InvalidArgumentException
41
     */
42 7
    public function __construct(array $options)
43
    {
44 7
        parent::__construct($options);
45
46 7
        if (!array_key_exists('client', $options)) {
47 1
            throw new \RuntimeException('No client specified in options');
48
        }
49
50 6
        $this->client = $options['client'];
51 6
    }
52
53
    /**
54
     * Write a message to the log.
55
     *
56
     * @param array $event log data event
57
     */
58 5
    protected function doWrite(array $event)
59
    {
60 5
        $priority = $this->priorityMap[$event['priority']];
61
62 5
        $extra = $event['extra'];
63 5
        if ($extra instanceof \Traversable) {
64 1
            $extra = iterator_to_array($extra);
65 5
        } elseif (!is_array($extra)) {
66 1
            $extra = [];
67 1
        }
68
69 5
        if ($this->contextContainsException($extra)) {
70
            /** @var \Throwable $exception */
71 2
            $exception = $extra['exception'];
72 2
            unset($extra['exception']);
73
74 2
            if ($event['message'] !== $exception->getMessage()) {
75 1
                $exception = new ContextException($event['message'], $exception->getCode(), $exception);
76 1
            }
77
78 2
            $this->client->getRaven()->captureException(
79 2
                $exception,
80
                [
81 2
                    'extra' => $this->sanitizeContextData($extra),
82 2
                    'level' => $priority,
83
                ]
84 2
            );
85
86 2
            return;
87
        }
88
89 3
        $stack = isset($extra['stack']) && is_array($extra['stack']) ? $extra['stack'] : null;
90
91 3
        if (!$stack) {
92 3
            $stack = $this->cleanBacktrace(debug_backtrace());
93 3
            if (!count($stack)) {
94
                $stack = false;
95
            }
96 3
        }
97
98 3
        $this->client->getRaven()->captureMessage(
99 3
            $event['message'],
100 3
            $this->sanitizeContextData($extra),
101 3
            $priority,
102
            $stack
0 ignored issues
show
Bug introduced by
It seems like $stack can also be of type array; however, Raven_Client::captureMessage() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
103 3
        );
104 3
    }
105
106
    /**
107
     * Remove first backtrace items until it founds something different from loggers
108
     *
109
     * @param array $backtrace
110
     * @return array
111
     */
112 3
    protected function cleanBacktrace(array $backtrace)
113
    {
114
        $excludeNamespaces = [
115 3
            'Facile\SentryModule\Log\\',
116 3
            'Psr\Log\\',
117
            'Zend\Log\\'
118 3
        ];
119
120 3
        $lastItem = null;
121 3
        while (count($backtrace)) {
122 3
            $item = $backtrace[0];
123 3
            if (!array_key_exists('class', $item)) {
124
                break;
125
            }
126 3
            $exclude = false;
127 3
            foreach ($excludeNamespaces as $namespace) {
128 3
                if (0 === strpos($item['class'], $namespace)) {
129 3
                    $exclude = true;
130 3
                    break;
131
                }
132 3
            }
133 3
            if (!$exclude) {
134 3
                break;
135
            }
136
137 3
            $lastItem = array_shift($backtrace);
138 3
        };
139
140 3
        if ($lastItem) {
141 3
            array_unshift($backtrace, $lastItem);
142 3
        }
143
144 3
        return $backtrace;
145
    }
146
147
    /**
148
     * @param array $context
149
     *
150
     * @return array
151
     */
152 5
    protected function sanitizeContextData(array $context)
153
    {
154 5
        array_walk_recursive($context, [$this, 'sanitizeContextItem']);
155
156 5
        return $context;
157
    }
158
159
    /**
160
     * @param mixed $value
161
     */
162 4
    protected function sanitizeContextItem(&$value)
163
    {
164 4
        if ($value instanceof \Traversable) {
165 1
            $value = $this->sanitizeContextData(iterator_to_array($value));
166 1
        }
167 4
        if (is_object($value)) {
168 2
            $value = method_exists($value, '__toString') ? (string) $value : get_class($value);
169 4
        } elseif (is_resource($value)) {
170 2
            $value = get_resource_type($value);
171 2
        }
172 4
    }
173
174
    /**
175
     * @param mixed $object
176
     *
177
     * @return bool
178
     */
179 2
    protected function objectIsThrowable($object)
180
    {
181 2
        return $object instanceof \Throwable || $object instanceof \Exception;
182
    }
183
184
    /**
185
     * @param array $context
186
     *
187
     * @return bool
188
     */
189 5
    protected function contextContainsException(array $context)
190
    {
191 5
        if (!array_key_exists('exception', $context)) {
192 3
            return false;
193
        }
194
195 2
        return $this->objectIsThrowable($context['exception']);
196
    }
197
}
198