Completed
Push — master ( 5c1b13...31e44f )
by Markus
07:15 queued 07:12
created

Module.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * Bright Answer ZendSentry
5
 *
6
 * This source file is part of the Bright Answer ZendSentry package
7
 *
8
 * @package    ZendSentry\Module
9
 * @license    MIT License {@link /docs/LICENSE}
10
 * @copyright  Copyright (c) 2018, Bright Answer OÜ
11
 */
12
13
namespace ZendSentry;
14
15
use Zend\EventManager\EventManager;
16
use Zend\Mvc\MvcEvent;
17
use ZendSentry\Mvc\View\Http\ExceptionStrategy as SentryHttpStrategy;
18
use ZendSentry\Mvc\View\Console\ExceptionStrategy as SentryConsoleStrategy;
19
use Zend\Mvc\View\Http\ExceptionStrategy;
20
use Raven_Client as Raven;
21
use Zend\Log\Logger;
22
23
/**
24
 * Class Module
25
 *
26
 * @package ZendSentry
27
 */
28
class Module
29
{
30
    /**
31
     * Translates Zend Framework log levels to Raven log levels.
32
     */
33
    private $logLevels = [
34
        7 => Raven::DEBUG,
35
        6 => Raven::INFO,
36
        5 => Raven::INFO,
37
        4 => Raven::WARNING,
38
        3 => Raven::ERROR,
39
        2 => Raven::FATAL,
40
        1 => Raven::FATAL,
41
        0 => Raven::FATAL,
42
    ];
43
44
    /**
45
     * @var Raven $ravenClient
46
     */
47
    protected $ravenClient;
48
49
    /**
50
     * @var ZendSentry $zendSentry
51
     */
52
    protected $zendSentry;
53
54
    /**
55
     * @var $config
56
     */
57
    protected $config;
58
59
    /**
60
     * @var EventManager $eventManager
61
     */
62
    protected $eventManager;
63
64
    /**
65
     * @param MvcEvent $event
66
     */
67
    public function onBootstrap(MvcEvent $event): void
68
    {
69
        // Setup RavenClient (provided by Sentry) and Sentry (provided by this module)
70
        $this->config = $event->getApplication()->getServiceManager()->get('Config');
71
72
        if (!$this->config['zend-sentry']['use-module']) {
73
            return;
74
        }
75
76
        if (isset($this->config['zend-sentry']['raven-config']) && \is_array($this->config['zend-sentry']['raven-config'])) {
77
            $ravenConfig = $this->config['zend-sentry']['raven-config'];
78
        } else {
79
            $ravenConfig = [];
80
        }
81
82
        $sentryApiKey = $this->config['zend-sentry']['sentry-api-key'];
83
        $ravenClient = new Raven($sentryApiKey, $ravenConfig);
84
85
        // Register the RavenClient as a application wide service
86
        /** @noinspection PhpUndefinedMethodInspection */
87
        $event->getApplication()->getServiceManager()->setService('raven', $ravenClient);
88
        $this->ravenClient = $ravenClient;
89
        $this->zendSentry = new ZendSentry($ravenClient);
90
91
        // Get the eventManager and set it as a member for convenience
92
        $this->eventManager = $event->getApplication()->getEventManager();
93
94
        // If ZendSentry is configured to use the custom logger, attach the listener
95
        if ($this->config['zend-sentry']['attach-log-listener']) {
96
            $this->setupBasicLogging($event);
97
        }
98
99
        // If ZendSentry is configured to log exceptions, a few things need to be set up
100
        if ($this->config['zend-sentry']['handle-exceptions']) {
101
            $this->setupExceptionLogging($event);
102
        }
103
104
        // If ZendSentry is configured to log errors, register it as error handler
105
        if ($this->config['zend-sentry']['handle-errors']) {
106
            $errorReportingLevel = $this->config['zend-sentry']['error-reporting'] ?? -1;
107
            $this->zendSentry->registerErrorHandler($this->config['zend-sentry']['call-existing-error-handler'], $errorReportingLevel);
108
        }
109
110
        // If ZendSentry is configured to log shutdown errors, register it
111
        if ($this->config['zend-sentry']['handle-shutdown-errors']) {
112
            $this->zendSentry->registerShutdownFunction();
113
        }
114
115
        // If ZendSentry is configured to log Javascript errors, add needed scripts to the view
116
        if ($this->config['zend-sentry']['handle-javascript-errors']) {
117
            $this->setupJavascriptLogging($event);
118
        }
119
    }
120
121
    /**
122
     * @return array
123
     */
124
    public function getAutoloaderConfig(): array
125
    {
126
        return [
127
            'Zend\Loader\StandardAutoloader' => [
128
                'namespaces' => [
129
                __NAMESPACE__ => __DIR__.'/src/'.__NAMESPACE__,
130
                ]
131
            ]
132
        ];
133
    }
134
135
    /**
136
     * @return mixed
137
     */
138
    public function getConfig()
139
    {
140
        return include __DIR__.'/config/module.config.php';
141
    }
142
143
    /**
144
     * Gives us the possibility to write logs to Sentry from anywhere in the application
145
     * Doesn't use the ZF compatible Log Writer because we want to return the Sentry event ID
146
     * ZF Logging doesn't provide the possibility to return values from writers
147
     *
148
     * @param MvcEvent $event
149
     */
150
    protected function setupBasicLogging(MvcEvent $event): void
151
    {
152
        // Get the shared event manager and attach a logging listener for the log event on application level
153
        $sharedManager = $this->eventManager->getSharedManager();
154
        $raven = $this->ravenClient;
155
        $logLevels = $this->logLevels;
156
157
        $sharedManager->attach('*', 'log', function($event) use ($raven, $logLevels) {
158
            /** @var $event MvcEvent */
159
            if (\is_object($event->getTarget())) {
160
                $target = \get_class($event->getTarget());
161
            } else {
162
                $target = (string) $event->getTarget();
163
            }
164
            $message  = $event->getParam('message', 'No message provided');
165
            $priority = (int) $event->getParam('priority', Logger::INFO);
166
            $message  = sprintf('%s: %s', $target, $message);
167
            $tags     = $event->getParam('tags', []);
168
            $extra   = $event->getParam('extra', []);
169
            $eventID = $raven->captureMessage($message, [], ['tags' => $tags, 'level' => $logLevels[$priority], 'extra' => $extra]
170
);
171
            return $eventID;
172
        }, 2);
173
    }
174
175
    /**
176
     * 1. Registers Sentry as exception handler
177
     * 2. Replaces the default ExceptionStrategy so Exception that are caught by Zend Framework can still be logged
178
     *
179
     * @param MvcEvent $event
180
     */
181
    protected function setupExceptionLogging(MvcEvent $event): void
182
    {
183
        // Register Sentry as exception handler for exception that bubble up to the top
184
        $this->zendSentry->registerExceptionHandler($this->config['zend-sentry']['call-existing-exception-handler']);
185
186
        // Replace the default ExceptionStrategy with ZendSentry's strategy
187
        if ($event->getApplication()->getServiceManager()->has('HttpExceptionStrategy')) {
188
            /** @var $exceptionStrategy ExceptionStrategy */
189
            $exceptionStrategy = $event->getApplication()->getServiceManager()->get('HttpExceptionStrategy');
190
            $exceptionStrategy->detach($this->eventManager);
191
        }
192
193
        // Check if script is running in console
194
        $exceptionStrategy = (PHP_SAPI == 'cli') ? new SentryConsoleStrategy() : new SentryHttpStrategy();
195
        $exceptionStrategy->attach($this->eventManager);
196
        $exceptionStrategy->setDisplayExceptions($this->config['zend-sentry']['display-exceptions']);
197
        $exceptionStrategy->setDefaultExceptionMessage($this->config['zend-sentry'][(PHP_SAPI == 'cli') ? 'default-exception-console-message' : 'default-exception-message']);
198
        if ($exceptionStrategy instanceof SentryHttpStrategy && isset($this->config['view_manager']['exception_template'])) {
199
            $exceptionStrategy->setExceptionTemplate($this->config['view_manager']['exception_template']);
1 ignored issue
show
The method setExceptionTemplate does only exist in ZendSentry\Mvc\View\Http\ExceptionStrategy, but not in ZendSentry\Mvc\View\Console\ExceptionStrategy.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
200
        }
201
        $ravenClient = $this->ravenClient;
202
203
        // Attach an exception listener for the ZendSentry exception strategy, can be triggered from anywhere else too
204
        $this->eventManager->getSharedManager()->attach('*', 'logException', function($event) use ($ravenClient) {
205
            /** @var $event MvcEvent */
206
            $exception = $event->getParam('exception');
207
            $tags = $event->getParam('tags', []);
208
            return $ravenClient->captureException($exception, ['tags' => $tags]);
209
        });
210
    }
211
212
    /**
213
     * Adds the necessary javascript, tries to prepend
214
     *
215
     * @param MvcEvent $event
216
     */
217
    protected function setupJavascriptLogging(MvcEvent $event): void
218
    {
219
        $viewHelper = $event->getApplication()->getServiceManager()->get('ViewHelperManager')->get('headscript');
220
        $useRavenjsCDN = $this->config['zend-sentry']['use-ravenjs-cdn'];
221
        if (!isset($useRavenjsCDN) || $useRavenjsCDN) {
222
            /** @noinspection PhpUndefinedMethodInspection */
223
            $viewHelper->offsetSetFile(0, '//cdn.ravenjs.com/3.26.2/raven.min.js');
224
        }
225
        $publicApiKey = $this->convertKeyToPublic($this->config['zend-sentry']['sentry-api-key']);
226
        $ravenjsConfig = json_encode($this->config['zend-sentry']['ravenjs-config']);
227
        /** @noinspection PhpUndefinedMethodInspection */
228
        $viewHelper->offsetSetScript(1, sprintf("if (typeof Raven !== 'undefined') Raven.config('%s', %s).install()", $publicApiKey, $ravenjsConfig));
229
    }
230
231
    /**
232
     * @param string $key
233
     * @return string $publicKey
234
     */
235
    private function convertKeyToPublic($key): string
236
    {
237
        // If new DSN is configured, no converting is needed
238
        if (substr_count($key, ':') == 1) {
239
            return $key;
240
        }
241
        // If legacy DSN with private part is configured...
242
        // ...find private part
243
        $start = strpos($key, ':', 6);
244
        $end = strpos($key, '@');
245
        $privatePart = substr($key, $start, $end - $start);
246
247
        // ... replace it with an empty string
248
        return str_replace($privatePart, '', $key);
249
    }
250
}
251