Client::doSend()   A
last analyzed

Complexity

Conditions 5
Paths 7

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 25
rs 9.4555
cc 5
nc 7
nop 3
1
<?php
2
3
namespace PortlandLabs\Slackbot\Slack\Api;
4
5
use CL\Slack\Exception\SlackException;
6
use CL\Slack\Payload\FilesUploadPayload;
7
use CL\Slack\Payload\PayloadInterface;
8
use CL\Slack\Payload\PayloadResponseInterface;
9
use CL\Slack\Serializer\PayloadResponseSerializer;
10
use CL\Slack\Serializer\PayloadSerializer;
11
use CL\Slack\Transport\ApiClientInterface;
12
use GuzzleHttp\ClientInterface;
13
use GuzzleHttp\Exception\GuzzleException;
14
use GuzzleHttp\Psr7\Request;
15
use PortlandLabs\Slackbot\Slack\Api\Payload\Builder;
16
use Psr\Http\Message\RequestInterface;
17
use Psr\Http\Message\ResponseInterface;
18
use Psr\Log\LoggerAwareTrait;
19
use Psr\Log\LoggerInterface;
20
21
/**
22
 * This class is largely written by Cas Leentfaar
23
 *
24
 * @author Cas Leentfaar <[email protected]>
25
 */
26
class Client implements ApiClientInterface
27
{
28
29
    /**
30
     * The (base) URL used for all communication with the Slack API.
31
     */
32
    const API_BASE_URL = 'https://slack.com/api/';
33
34
    /**
35
     * Event triggered just before it's sent to the Slack API
36
     * Any listeners are passed the request data (array) as the first argument.
37
     */
38
    const EVENT_REQUEST = 'EVENT_REQUEST';
39
40
    /**
41
     * Event triggered just before it's sent to the Slack API
42
     * Any listeners are passed the response data (array) as the first argument.
43
     */
44
    const EVENT_RESPONSE = 'EVENT_RESPONSE';
45
46
47
    use LoggerAwareTrait;
48
49
    /**
50
     * @var string|null
51
     */
52
    protected $token;
53
54
    /**
55
     * @var PayloadSerializer
56
     */
57
    protected $payloadSerializer;
58
59
    /**
60
     * @var PayloadResponseSerializer
61
     */
62
    protected $payloadResponseSerializer;
63
64
    /**
65
     * @var ClientInterface
66
     */
67
    protected $client;
68
69
    /**
70
     * @var Builder
71
     */
72
    protected $builder;
73
74
    /**
75
     * @var string
76
     */
77
    protected $username;
78
79
    /**
80
     * @param string|null $token
81
     * @param ClientInterface|null $client
82
     * @param PayloadSerializer $serializer
83
     * @param PayloadResponseSerializer $responseSerializer
84
     * @param LoggerInterface $logger
85
     * @param Builder $builder
86
     */
87
    public function __construct(
88
        $token,
89
        ClientInterface $client,
90
        PayloadSerializer $serializer,
91
        PayloadResponseSerializer $responseSerializer,
92
        LoggerInterface $logger,
93
        Builder $builder
94
    )
95
    {
96
        $this->token = $token;
97
        $this->payloadSerializer = $serializer;
98
        $this->payloadResponseSerializer = $responseSerializer;
99
        $this->client = $client;
100
        $this->setLogger($logger);
101
        $this->builder = $builder;
102
    }
103
104
    /**
105
     * Send a payload using the client
106
     *
107
     * @param PayloadInterface $payload
108
     * @param null $token
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $token is correct as it would always require null to be passed?
Loading history...
109
     * @return PayloadResponseInterface
110
     * @throws SlackException
111
     * @throws GuzzleException
112
     */
113
    public function send(PayloadInterface $payload, $token = null)
114
    {
115
        try {
116
            if ($token === null && $this->token === null) {
117
                throw new \InvalidArgumentException('You must supply a token to send a payload, since you did not provide one during construction');
118
            }
119
120
            $content = $file = '';
121
            if ($payload instanceof FilesUploadPayload) {
122
                $content = $payload->getContent();
123
                $file = $payload->getFile();
124
                $payload->setContent('');
125
                $payload->setFile('');
126
            }
127
128
            $serializedPayload = $this->payloadSerializer->serialize($payload);
129
130
            if ($payload instanceof FilesUploadPayload) {
131
                $payload->setContent($content);
132
                $payload->setFile($file);
133
                $serializedPayload['content'] = $content;
134
                $serializedPayload['file'] = $file;
135
                $content = $file = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $file is dead and can be removed.
Loading history...
Unused Code introduced by
The assignment to $content is dead and can be removed.
Loading history...
136
            }
137
138
            $this->logger->debug('[API -> ] ' . $payload->getMethod() . ': ' . json_encode($serializedPayload));
0 ignored issues
show
Bug introduced by
The method debug() does not exist on null. ( Ignorable by Annotation )

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

138
            $this->logger->/** @scrutinizer ignore-call */ 
139
                           debug('[API -> ] ' . $payload->getMethod() . ': ' . json_encode($serializedPayload));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
139
140
            $responseData = $this->doSend($payload->getMethod(), $serializedPayload, $token);
141
            $this->logger->debug('[API <- ] ' . $payload->getMethod() . ': ' . json_encode($responseData));
142
143
            return $this->payloadResponseSerializer->deserialize($responseData, $payload->getResponseClass());
144
        } catch (\Exception $e) {
145
            throw new SlackException(sprintf('Failed to send payload: %s', $e->getMessage()), null, $e);
146
        }
147
    }
148
149
    /**
150
     * Handle sending data to the API
151
     *
152
     * @param string $method
153
     * @param array $data
154
     * @param null $token
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $token is correct as it would always require null to be passed?
Loading history...
155
     * @return array|mixed
156
     * @throws SlackException
157
     * @throws GuzzleException
158
     */
159
    private function doSend($method, array $data, $token = null)
160
    {
161
        try {
162
            $data['token'] = $token ?: $this->token;
0 ignored issues
show
introduced by
$token is of type null, thus it always evaluated to false.
Loading history...
163
164
            $request = $this->createRequest($method, $data);
165
166
            /** @var ResponseInterface $response */
167
            $response = $this->client->send($request);
168
        } catch (\Exception $e) {
169
            throw new SlackException('Failed to send data to the Slack API', null, $e);
170
        }
171
172
        try {
173
            $responseData = json_decode($response->getBody()->getContents(), true);
174
            if (!is_array($responseData)) {
175
                throw new \Exception(sprintf(
176
                    'Expected JSON-decoded response data to be of type "array", got "%s"',
177
                    gettype($responseData)
178
                ));
179
            }
180
181
            return $responseData;
182
        } catch (\Exception $e) {
183
            throw new SlackException('Failed to process response from the Slack API', null, $e);
184
        }
185
    }
186
187
    /**
188
     * @param string $method
189
     * @param array $payload
190
     *
191
     * @return RequestInterface
192
     */
193
    private function createRequest($method, array $payload)
194
    {
195
        $request = new Request(
196
            'POST',
197
            self::API_BASE_URL . $method,
198
            ['Content-Type' => 'application/x-www-form-urlencoded'],
199
            http_build_query($payload)
200
        );
201
202
        return $request;
203
    }
204
205
    /**
206
     * Set the API username to use
207
     *
208
     * @param string $username
209
     */
210
    public function setUsername(string $username)
211
    {
212
        $this->username = $username;
213
    }
214
215
    /**
216
     * Get the Payload builder object
217
     *
218
     * @return Builder
219
     */
220
    public function getBuilder()
221
    {
222
        return $this->builder->prepare($this->username);
223
    }
224
225
}