Completed
Push — master ( ef81da...491768 )
by Camilo
02:25
created

TgLog::composeApiMethodUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
cc 1
eloc 5
nc 1
nop 1
crap 1
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
        $response = $client->post($this->composeApiMethodUrl($method), $formData);
128
        $this->logger->debug('Got response back from Telegram, applying json_decode');
129
        return json_decode((string)$response->getBody(), true);
130
    }
131
132 22
    private function resetObjectValues(): TgLog
133
    {
134 22
        $this->formType = 'application/x-www-form-urlencoded';
135 22
        $this->methodName = '';
136
137 22
        return $this;
138
    }
139
140 22
    private function constructFormData(TelegramMethods $method): array
141
    {
142 22
        $result = $this->checkSpecialConditions($method);
143
144 22
        switch ($this->formType) {
145
            case 'application/x-www-form-urlencoded':
146 18
                $this->logger->debug('Creating x-www-form-urlencoded form');
147
                $formData = [
148 18
                    'form_params' => get_object_vars($method),
149
                ];
150 18
                break;
151
            case 'multipart/form-data':
152 4
                $formData = $this->buildMultipartFormData(get_object_vars($method), $result['id'], $result['stream']);
153 4
                break;
154
            default:
155
                $this->logger->critical('Invalid form-type detected');
156
                $formData = [];
157
                break;
158
        }
159
160 22
        return $formData;
161
    }
162
163
    /**
164
     * Can perform any special checks needed to be performed before sending the actual request to Telegram
165
     *
166
     * This will return an array with data that will be different in each case (for now). This can be changed in the
167
     * future.
168
     *
169
     * @param TelegramMethods $method
170
     * @return array
171
     */
172 22
    private function checkSpecialConditions(TelegramMethods $method): array
173
    {
174 22
        $this->logger->debug('Checking special conditions');
175 22
        $method->performSpecialConditions();
176
177 22
        $return = [false];
178
179 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...
180 20
            if (is_object($value)) {
181 4
                if (get_class($value) == 'unreal4u\\Telegram\\Types\\Custom\\InputFile') {
182 4
                    $this->logger->debug('About to send a file, so changing request to use multi-part instead');
183
                    // If we are about to send a file, we must use the multipart/form-data way
184 4
                    $this->formType = 'multipart/form-data';
185
                    $return = [
186 4
                        'id' => $key,
187 20
                        'stream' => $value->getStream(),
188
                    ];
189
                }
190
            }
191
        }
192
193 22
        return $return;
194
    }
195
196
    /**
197
     * Builds up the URL with which we can work with
198
     *
199
     * All methods in the Bot API are case-insensitive.
200
     * All queries must be made using UTF-8.
201
     *
202
     * @see https://core.telegram.org/bots/api#making-requests
203
     *
204
     * @param TelegramMethods $call
205
     * @return string
206
     */
207 22
    protected function composeApiMethodUrl(TelegramMethods $call): string
208
    {
209 22
        $completeClassName = get_class($call);
210 22
        $this->methodName = substr($completeClassName, strrpos($completeClassName, '\\') + 1);
211 22
        $this->logger->info('About to perform API request', ['method' => $this->methodName]);
212
213 22
        return $this->apiUrl . $this->methodName;
214
    }
215
216
    /**
217
     * Builds up a multipart form-like array for Guzzle
218
     *
219
     * @param array $data The original object in array form
220
     * @param string $fileKeyName A file handler will be sent instead of a string, state here which field it is
221
     * @param resource $stream The actual file handler
222
     * @return array Returns the actual formdata to be sent
223
     */
224 4
    private function buildMultipartFormData(array $data, string $fileKeyName, $stream): array
225
    {
226 4
        $this->logger->debug('Creating multi-part form array data');
227
        $formData = [
228 4
            'multipart' => [],
229
        ];
230
231 4
        foreach ($data as $id => $value) {
232
            // Always send as a string unless it's a file
233
            $multiPart = [
234 4
                'name' => $id,
235
                'contents' => null,
236
            ];
237
238 4
            if ($id === $fileKeyName) {
239 4
                $multiPart['contents'] = $stream;
240
            } else {
241 4
                $multiPart['contents'] = (string)$value;
242
            }
243
244 4
            $formData['multipart'][] = $multiPart;
245
        }
246
247 4
        return $formData;
248
    }
249
}
250