Completed
Push — master ( 501a02...c89e21 )
by Camilo
05:14
created

TgLog::checkSpecialConditions()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4
Metric Value
dl 0
loc 23
ccs 12
cts 12
cp 1
rs 8.7972
cc 4
eloc 13
nc 4
nop 1
crap 4
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace unreal4u;
6
7
use GuzzleHttp\Client;
8
use unreal4u\Abstracts\TelegramTypes;
9
use unreal4u\InternalFunctionality\DummyLogger;
10
use unreal4u\InternalFunctionality\TelegramDocument;
11
use unreal4u\Abstracts\TelegramMethods;
12
use unreal4u\Telegram\Types\File;
13
use Psr\Log\LoggerInterface;
14
15
/**
16
 * The main API which does it all
17
 */
18
class TgLog
19
{
20
    /**
21
     * Stores the token
22
     * @var string
23
     */
24
    private $botToken = '';
25
26
    /**
27
     * Contains an instance to a PSR-3 compatible logger
28
     * @var LoggerInterface
29
     */
30
    protected $logger = null;
31
32
    /**
33
     * Stores the API URL from Telegram
34
     * @var string
35
     */
36
    private $apiUrl = '';
37
38
    /**
39
     * With this flag we'll know what type of request to send to Telegram
40
     *
41
     * 'application/x-www-form-urlencoded' is the "normal" one, which is simpler and quicker.
42
     * 'multipart/form-data' should be used only to upload documents, photos, etc.
43
     *
44
     * @var string
45
     */
46
    private $formType = 'application/x-www-form-urlencoded';
47
48
    /**
49
     * Stores the last method name used
50
     * @var string
51
     */
52
    protected $methodName = '';
53
54
    /**
55
     * TelegramLog constructor.
56
     * @param string $botToken
57
     */
58 36
    public function __construct(string $botToken, LoggerInterface $logger = null)
59
    {
60 36
        $this->botToken = $botToken;
61
62 36
        if (is_null($logger)) {
63 36
            $logger = new DummyLogger();
64
        }
65 36
        $this->logger = $logger;
66
67 36
        $this->constructApiUrl();
68 36
    }
69
70
    /**
71
     * Prepares and sends an API request to Telegram
72
     *
73
     * @param TelegramMethods $method
74
     * @return TelegramTypes
75
     */
76 22
    public function performApiRequest(TelegramMethods $method): TelegramTypes
77
    {
78 22
        $this->logger->debug('Going to perform API request, resetting internal class values');
79 22
        $this->resetObjectValues();
80 22
        $jsonDecoded = $this->sendRequestToTelegram($method, $this->constructFormData($method));
81
82 17
        $returnObject = 'unreal4u\\Telegram\\Types\\' . $method::bindToObjectType();
83 17
        $this->logger->debug(sprintf('Decoded response from server, instantiating new %s class', $returnObject));
84 17
        return new $returnObject($jsonDecoded['result'], $this->logger);
85
    }
86
87
    /**
88
     * Will download a file from the Telegram server. Before calling this function, you have to call the getFile method!
89
     *
90
     * @see unreal4u\Telegram\Types\File
91
     * @see unreal4u\Telegram\Methods\GetFile
92
     *
93
     * @param File $file
94
     * @return TelegramDocument
95
     */
96
    public function downloadFile(File $file): TelegramDocument
97
    {
98
        $this->logger->debug('Downloading file from Telegram, creating URI');
99
        $url = $this->apiUrl . $file->file_path;
100
        $client = new Client();
101
        $this->logger->debug('About to perform request');
102
        return new TelegramDocument($client->get($url));
0 ignored issues
show
Compatibility introduced by
$client->get($url) of type object<Psr\Http\Message\ResponseInterface> is not a sub-type of object<GuzzleHttp\Psr7\Response>. It seems like you assume a concrete implementation of the interface Psr\Http\Message\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
103
    }
104
105
    /**
106
     * Builds up the Telegram API url
107
     * @return TgLog
108
     */
109 36
    final private function constructApiUrl(): TgLog
110
    {
111 36
        $this->apiUrl = 'https://api.telegram.org/bot' . $this->botToken . '/';
112 36
        $this->logger->debug('Built up the API URL');
113 36
        return $this;
114
    }
115
116
    /**
117
     * This is the method that actually makes the call, which can be easily overwritten so that our unit tests can work
118
     *
119
     * @param TelegramMethods $method
120
     * @param array $formData
121
     * @return array
122
     */
123
    protected function sendRequestToTelegram(TelegramMethods $method, array $formData): array
124
    {
125
        $this->logger->debug('About to instantiate HTTP Client');
126
        $client = new Client();
127
        $this->logger->info('About to perform POST to Telegram bot API');
128
        $response = $client->post($this->composeApiMethodUrl($method), $formData);
129
        $this->logger->debug('Got response back from Telegram, applying json_decode');
130
        return json_decode((string)$response->getBody(), true);
131
    }
132
133 22
    private function resetObjectValues(): TgLog
134
    {
135 22
        $this->formType = 'application/x-www-form-urlencoded';
136 22
        $this->methodName = '';
137
138 22
        return $this;
139
    }
140
141 22
    private function constructFormData(TelegramMethods $method): array
142
    {
143 22
        $result = $this->checkSpecialConditions($method);
144
145 22
        switch ($this->formType) {
146
            case 'application/x-www-form-urlencoded':
147 18
                $this->logger->debug('Creating x-www-form-urlencoded form');
148
                $formData = [
149 18
                    'form_params' => get_object_vars($method),
150
                ];
151 18
                break;
152
            case 'multipart/form-data':
153 4
                $formData = $this->buildMultipartFormData(get_object_vars($method), $result['id'], $result['stream']);
154 4
                break;
155
            default:
156
                $this->logger->critical('Invalid form-type detected');
157
                $formData = [];
158
                break;
159
        }
160
161 22
        return $formData;
162
    }
163
164
    /**
165
     * Can perform any special checks needed to be performed before sending the actual request to Telegram
166
     *
167
     * This will return an array with data that will be different in each case (for now). This can be changed in the
168
     * future.
169
     *
170
     * @param TelegramMethods $method
171
     * @return array
172
     */
173 22
    private function checkSpecialConditions(TelegramMethods $method): array
174
    {
175 22
        $this->logger->debug('Checking special conditions');
176 22
        $method->performSpecialConditions();
177
178 22
        $return = [false];
179
180 22
        foreach ($method as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $method of type object<unreal4u\Abstracts\TelegramMethods> is not traversable.
Loading history...
181 20
            if (is_object($value)) {
182 4
                if (get_class($value) == 'unreal4u\\Telegram\\Types\\Custom\\InputFile') {
183 4
                    $this->logger->debug('About to send a file, so changing request to use multi-part instead');
184
                    // If we are about to send a file, we must use the multipart/form-data way
185 4
                    $this->formType = 'multipart/form-data';
186
                    $return = [
187 4
                        'id' => $key,
188 20
                        'stream' => $value->getStream(),
189
                    ];
190
                }
191
            }
192
        }
193
194 22
        return $return;
195
    }
196
197
    /**
198
     * Builds up the URL with which we can work with
199
     *
200
     * All methods in the Bot API are case-insensitive.
201
     * All queries must be made using UTF-8.
202
     *
203
     * @see https://core.telegram.org/bots/api#making-requests
204
     *
205
     * @param TelegramMethods $call
206
     * @return string
207
     */
208 22
    protected function composeApiMethodUrl(TelegramMethods $call): string
209
    {
210 22
        $completeClassName = get_class($call);
211 22
        $this->methodName = substr($completeClassName, strrpos($completeClassName, '\\') + 1);
212 22
        $this->logger->debug(sprintf('Selected API method is %s', $this->methodName));
213
214 22
        return $this->apiUrl . $this->methodName;
215
    }
216
217
    /**
218
     * Builds up a multipart form-like array for Guzzle
219
     *
220
     * @param array $data The original object in array form
221
     * @param string $fileKeyName A file handler will be sent instead of a string, state here which field it is
222
     * @param resource $stream The actual file handler
223
     * @return array Returns the actual formdata to be sent
224
     */
225 4
    private function buildMultipartFormData(array $data, string $fileKeyName, $stream): array
226
    {
227 4
        $this->logger->debug('Creating multi-part form array data');
228
        $formData = [
229 4
            'multipart' => [],
230
        ];
231
232 4
        foreach ($data as $id => $value) {
233
            // Always send as a string unless it's a file
234
            $multiPart = [
235 4
                'name' => $id,
236
                'contents' => null,
237
            ];
238
239 4
            if ($id === $fileKeyName) {
240 4
                $multiPart['contents'] = $stream;
241
            } else {
242 4
                $multiPart['contents'] = (string)$value;
243
            }
244
245 4
            $formData['multipart'][] = $multiPart;
246
        }
247
248 4
        return $formData;
249
    }
250
}
251