Webhook   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 69
c 6
b 0
f 0
dl 0
loc 232
ccs 55
cts 55
cp 1
rs 10
wmc 20

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A getSubscribedEvents() 0 4 1
A fireRequest() 0 29 5
A onPhpbuEnd() 0 9 2
A getBodyFormatter() 0 11 3
A getQueryStringData() 0 11 2
A setup() 0 22 4
A buildGetUri() 0 4 2
1
<?php
2
namespace phpbu\App\Log;
3
4
use phpbu\App\Exception;
5
use phpbu\App\Event;
6
use phpbu\App\Listener;
7
use phpbu\App\Result;
8
use phpbu\App\Util\Arr;
9
use Throwable;
10
11
/**
12
 * Webhook Logger
13
 *
14
 * @package    phpbu
15
 * @subpackage Log
16
 * @author     Cees Vogel <[email protected]>
17
 * @author     Sebastian Feldmann <[email protected]>
18
 * @copyright  Sebastian Feldmann <[email protected]>
19
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
20
 * @link       https://phpbu.de/
21
 * @since      Class available since Release 5.0.0
22
 */
23
class Webhook implements Listener, Logger
24
{
25
    /**
26
     * Start time
27
     *
28
     * @var float
29
     */
30
    private $start;
31
32
    /**
33
     * Uri to call
34
     *
35
     * @var string
36
     */
37
    private $uri;
38
39
    /**
40
     * Request method GET|POST
41
     *
42
     * @var string
43
     */
44
    private $method;
45
46
    /**
47
     * Request timeout (seconds)
48
     *
49
     * @var int
50
     */
51
    private $timeout;
52
53
    /**
54
     * Basic auth username
55
     *
56
     * @var string
57
     */
58
    private $username;
59
60
    /**
61
     * Basic auth password
62
     *
63
     * @var string
64
     */
65
    private $password;
66
67
    /**
68
     * Request content type
69
     *
70
     * @var string
71
     */
72
    private $contentType;
73
74
    /**
75
     * Body template to use
76
     *
77
     * @var string
78
     */
79
    private $template;
80
81
    /**
82
     * List of available default formatter
83
     *
84
     * @var array
85
     */
86
    private $availableFormatter = [
87
        'multipart/form-data' => '\\phpbu\\App\\Log\\ResultFormatter\\FormData',
88 7
        'application/json'    => '\\phpbu\\App\\Log\\ResultFormatter\\Json',
89
        'application/xml'     => '\\phpbu\\App\\Log\\ResultFormatter\\Xml'
90 7
    ];
91 7
92
    /**
93
     * Constructor will only set the start time to be able to log duration
94
     */
95
    public function __construct()
96
    {
97
        $this->start = microtime(true);
98
    }
99
100
    /**
101
     * Returns an array of event names this subscriber wants to listen to.
102
     *
103
     * The array keys are event names and the value can be:
104
     *
105 1
     *  - The method name to call (priority defaults to 0)
106
     *  - An array composed of the method name to call and the priority
107
     *  - An array of arrays composed of the method names to call and respective
108 1
     *    priorities, or 0 if unset
109
     *
110
     * @return array The event names to listen to
111
     */
112
    public static function getSubscribedEvents(): array
113
    {
114
        return [
115
            'phpbu.app_end' => 'onPhpbuEnd',
116
        ];
117
    }
118
119 7
    /**
120
     * Setup the logger.
121 7
     *
122 1
     * @see    \phpbu\App\Log\Logger::setup
123
     * @param  array $options
124 6
     * @throws \phpbu\App\Exception
125 6
     */
126 6
    public function setup(array $options)
127 6
    {
128 6
        if (empty($options['uri'])) {
129 6
            throw new Exception('no uri given');
130 6
        }
131
132
        // PHP >7.2 deprecated the filter options and enabled them by default
133
        $filterOptions = version_compare(PHP_VERSION, '7.2.0', '<')
134
                       ? FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED
0 ignored issues
show
introduced by
The constant FILTER_FLAG_SCHEME_REQUIRED has been deprecated: 7.3.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

134
                       ? /** @scrutinizer ignore-deprecated */ FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED
Loading history...
135
                       : null;
136
137 6
        if (!filter_var($options['uri'], FILTER_VALIDATE_URL, $filterOptions)) {
138
            throw new Exception('webhook URI is invalid');
139 6
        }
140 6
141 6
        $this->uri             = $options['uri'];
142 6
        $this->method          = Arr::getValue($options, 'method', 'GET');
143 5
        $this->username        = Arr::getValue($options, 'username', '');
144
        $this->password        = Arr::getValue($options, 'password', '');
145 5
        $this->template        = Arr::getValue($options, 'template', '');
146 1
        $this->contentType     = Arr::getValue($options, 'contentType', 'multipart/form-data');
147
        $this->timeout         = Arr::getValue($options, 'timeout', '');
0 ignored issues
show
Documentation Bug introduced by
It seems like phpbu\App\Util\Arr::getV...options, 'timeout', '') can also be of type string. However, the property $timeout is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
148
    }
149
150
    /**
151
     * phpbu end event.
152
     *
153
     * @param \phpbu\App\Event\App\End $event
154 2
     */
155
    public function onPhpbuEnd(Event\App\End $event)
156 2
    {
157 2
        $result    = $event->getResult();
158
        $data      = $this->getQueryStringData($result);
159
        $uri       = $this->method === 'GET' ? $this->buildGetUri($data) : $this->uri;
160
        $formatter = $this->getBodyFormatter();
161
        $body      = $formatter->format($result);
162
163
        $this->fireRequest($uri, $body);
164
    }
165
166
    /**
167 6
     * Builds the final request uri for GET requests.
168
     *
169 6
     * @param  array $data
170 1
     * @return string
171
     */
172
    private function buildGetUri(array $data) : string
173 5
    {
174 1
        $glue = strpos($this->uri, '?') !== false ? '&' : '?';
175
        return $this->uri . $glue . http_build_query($data);
176 4
    }
177 4
178
    /**
179
     * Return the request body template.
180
     * If template and body are set the body supersedes the template setting.
181
     *
182
     * @return \phpbu\App\Log\ResultFormatter
183
     * @throws \phpbu\App\Exception
184
     */
185
    private function getBodyFormatter() : ResultFormatter
186 6
    {
187
        if (!empty($this->template)) {
188 6
            return new ResultFormatter\Template($this->template);
189
        }
190
191 6
        if (!isset($this->availableFormatter[$this->contentType])) {
192 6
            throw new Exception('no default formatter for content-type: ' . $this->contentType);
193 6
        }
194 6
        $class = $this->availableFormatter[$this->contentType];
195 6
        return new $class();
196 6
    }
197
198
    /**
199
     * Returns some basic statistics as GET query string.
200
     *
201
     * @param  \phpbu\App\Result $result
202
     * @return array
203
     */
204
    private function getQueryStringData(Result $result) : array
205
    {
206
        $end = microtime(true);
207
208 5
        return [
209
            'status'    => $result->allOk() ? 0 : 1,
210 5
            'timestamp' => (int) $this->start,
211
            'duration'  => round($end - $this->start, 4),
212
            'err-cnt'   => $result->errorCount(),
213 5
            'bak-cnt'   => count($result->getBackups()),
214
            'bak-fail'  => $result->backupsFailedCount(),
215
        ];
216
    }
217 5
218 5
219 5
    /**
220
     * Execute the request to the webhook uri.
221
     *
222 5
     * @param  string $uri
223 1
     * @param  string $body
224
     * @throws \phpbu\App\Exception
225
     */
226 5
    protected function fireRequest(string $uri, string $body = '')
227 5
    {
228
        $headers = [];
229
        $options = [
230 5
            'http' => [
231 4
                'method'  => strtoupper($this->method),
232 4
            ]
233
        ];
234 1
235
        if (!empty($body)) {
236
            $headers[]                  = 'Content-Type: ' . $this->contentType;
237
            $options['http']['content'] = $body;
238
        }
239
240
        if (!empty($this->timeout)) {
241
            $options['http']['timeout'] = $this->timeout;
242
        }
243
244
        if (!empty($this->username)) {
245
            $headers[] = 'Authorization: Basic ' . base64_encode($this->username . ':' . $this->password);
246
        }
247
248
        $options['http']['header'] = implode("\r\n", $headers);
249
        $context                   = stream_context_create($options);
250
251
        try {
252
            file_get_contents($uri, false, $context);
253
        } catch (Throwable $t) {
254
            throw new Exception('could not reach webhook: ' . $this->uri);
255
        }
256
    }
257
}
258