Completed
Push — master ( bef0b2...4bbbeb )
by Camilo
07:23 queued 04:52
created

TgLog::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.4285
cc 2
eloc 7
nc 2
nop 2
crap 2
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace unreal4u;
6
7
use GuzzleHttp\Client;
8
use GuzzleHttp\ClientInterface;
9
use unreal4u\Abstracts\TelegramTypes;
10
use unreal4u\InternalFunctionality\DummyLogger;
11
use unreal4u\InternalFunctionality\TelegramDocument;
12
use unreal4u\Abstracts\TelegramMethods;
13
use unreal4u\Telegram\Types\File;
14
use Psr\Log\LoggerInterface;
15
16
/**
17
 * The main API which does it all
18
 */
19
class TgLog
20
{
21
    /**
22
     * @var ClientInterface
23
     */
24
    protected $httpClient;
25
26
    /**
27
     * Stores the token
28
     * @var string
29
     */
30
    private $botToken = '';
31
32
    /**
33
     * Contains an instance to a PSR-3 compatible logger
34
     * @var LoggerInterface
35
     */
36
    protected $logger = null;
37
38
    /**
39
     * Stores the API URL from Telegram
40
     * @var string
41
     */
42
    private $apiUrl = '';
43
44
    /**
45
     * With this flag we'll know what type of request to send to Telegram
46
     *
47
     * 'application/x-www-form-urlencoded' is the "normal" one, which is simpler and quicker.
48
     * 'multipart/form-data' should be used only to upload documents, photos, etc.
49
     *
50
     * @var string
51
     */
52
    private $formType = 'application/x-www-form-urlencoded';
53
54
    /**
55
     * Stores the last method name used
56
     * @var string
57
     */
58
    protected $methodName = '';
59
60
    /**
61
     * TelegramLog constructor.
62
     * @param string $botToken
63
     */
64 36
    public function __construct(string $botToken, LoggerInterface $logger = null)
65
    {
66 36
        $this->botToken = $botToken;
67
68 36
        if (is_null($logger)) {
69 36
            $logger = new DummyLogger();
70
        }
71 36
        $this->logger = $logger;
72 36
        $this->httpClient = new Client();
73
74 36
        $this->constructApiUrl();
75 36
    }
76
77
    /**
78
     * Prepares and sends an API request to Telegram
79
     *
80
     * @param TelegramMethods $method
81
     * @return TelegramTypes
82
     */
83 22
    public function performApiRequest(TelegramMethods $method): TelegramTypes
84
    {
85 22
        $this->logger->debug('Going to perform API request, resetting internal class values');
86 22
        $this->resetObjectValues();
87 22
        $jsonDecoded = $this->sendRequestToTelegram($method, $this->constructFormData($method));
88
89 17
        $returnObject = 'unreal4u\\Telegram\\Types\\' . $method::bindToObjectType();
90 17
        $this->logger->debug(sprintf('Decoded response from server, instantiating new %s class', $returnObject));
91 17
        return new $returnObject($jsonDecoded['result'], $this->logger);
92
    }
93
94
    /**
95
     * Will download a file from the Telegram server. Before calling this function, you have to call the getFile method!
96
     *
97
     * @see unreal4u\Telegram\Types\File
98
     * @see unreal4u\Telegram\Methods\GetFile
99
     *
100
     * @param File $file
101
     * @return TelegramDocument
102
     */
103
    public function downloadFile(File $file): TelegramDocument
104
    {
105
        $this->logger->debug('Downloading file from Telegram, creating URI');
106
        $url = 'https://api.telegram.org/file/bot' . $this->botToken . '/' . $file->file_path;
107
        $this->logger->debug('About to perform request');
108
        return new TelegramDocument($this->httpClient->get($url));
109
    }
110
111
    /**
112
     * Builds up the Telegram API url
113
     * @return TgLog
114
     */
115 36
    final private function constructApiUrl(): TgLog
116
    {
117 36
        $this->apiUrl = 'https://api.telegram.org/bot' . $this->botToken . '/';
118 36
        $this->logger->debug('Built up the API URL');
119 36
        return $this;
120
    }
121
122
    /**
123
     * This is the method that actually makes the call, which can be easily overwritten so that our unit tests can work
124
     *
125
     * @param TelegramMethods $method
126
     * @param array $formData
127
     * @return array
128
     */
129
    protected function sendRequestToTelegram(TelegramMethods $method, array $formData): array
130
    {
131
        $this->logger->debug('About to instantiate HTTP Client');
132
        $response = $this->httpClient->post($this->composeApiMethodUrl($method), $formData);
133
        $this->logger->debug('Got response back from Telegram, applying json_decode');
134
        return json_decode((string)$response->getBody(), true);
135
    }
136
137 22
    private function resetObjectValues(): TgLog
138
    {
139 22
        $this->formType = 'application/x-www-form-urlencoded';
140 22
        $this->methodName = '';
141
142 22
        return $this;
143
    }
144
145 22
    private function constructFormData(TelegramMethods $method): array
146
    {
147 22
        $result = $this->checkSpecialConditions($method);
148
149 22
        switch ($this->formType) {
150 22
            case 'application/x-www-form-urlencoded':
151 18
                $this->logger->debug('Creating x-www-form-urlencoded form');
152
                $formData = [
153 18
                    'form_params' => get_object_vars($method),
154
                ];
155 18
                break;
156 4
            case 'multipart/form-data':
157 4
                $formData = $this->buildMultipartFormData(get_object_vars($method), $result['id'], $result['stream']);
158 4
                break;
159
            default:
160
                $this->logger->critical('Invalid form-type detected');
161
                $formData = [];
162
                break;
163
        }
164
165 22
        return $formData;
166
    }
167
168
    /**
169
     * Can perform any special checks needed to be performed before sending the actual request to Telegram
170
     *
171
     * This will return an array with data that will be different in each case (for now). This can be changed in the
172
     * future.
173
     *
174
     * @param TelegramMethods $method
175
     * @return array
176
     */
177 22
    private function checkSpecialConditions(TelegramMethods $method): array
178
    {
179 22
        $this->logger->debug('Checking special conditions');
180 22
        $method->performSpecialConditions();
181
182 22
        $return = [false];
183
184 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...
185 20
            if (is_object($value)) {
186 4
                if (get_class($value) == 'unreal4u\\Telegram\\Types\\Custom\\InputFile') {
187 4
                    $this->logger->debug('About to send a file, so changing request to use multi-part instead');
188
                    // If we are about to send a file, we must use the multipart/form-data way
189 4
                    $this->formType = 'multipart/form-data';
190
                    $return = [
191 4
                        'id' => $key,
192 20
                        'stream' => $value->getStream(),
193
                    ];
194
                }
195
            }
196
        }
197
198 22
        return $return;
199
    }
200
201
    /**
202
     * Builds up the URL with which we can work with
203
     *
204
     * All methods in the Bot API are case-insensitive.
205
     * All queries must be made using UTF-8.
206
     *
207
     * @see https://core.telegram.org/bots/api#making-requests
208
     *
209
     * @param TelegramMethods $call
210
     * @return string
211
     */
212 22
    protected function composeApiMethodUrl(TelegramMethods $call): string
213
    {
214 22
        $completeClassName = get_class($call);
215 22
        $this->methodName = substr($completeClassName, strrpos($completeClassName, '\\') + 1);
216 22
        $this->logger->info('About to perform API request', ['method' => $this->methodName]);
217
218 22
        return $this->apiUrl . $this->methodName;
219
    }
220
221
    /**
222
     * Builds up a multipart form-like array for Guzzle
223
     *
224
     * @param array $data The original object in array form
225
     * @param string $fileKeyName A file handler will be sent instead of a string, state here which field it is
226
     * @param resource $stream The actual file handler
227
     * @return array Returns the actual formdata to be sent
228
     */
229 4
    private function buildMultipartFormData(array $data, string $fileKeyName, $stream): array
230
    {
231 4
        $this->logger->debug('Creating multi-part form array data');
232
        $formData = [
233 4
            'multipart' => [],
234
        ];
235
236 4
        foreach ($data as $id => $value) {
237
            // Always send as a string unless it's a file
238
            $multiPart = [
239 4
                'name' => $id,
240
                'contents' => null,
241
            ];
242
243 4
            if ($id === $fileKeyName) {
244 4
                $multiPart['contents'] = $stream;
245
            } else {
246 4
                $multiPart['contents'] = (string)$value;
247
            }
248
249 4
            $formData['multipart'][] = $multiPart;
250
        }
251
252 4
        return $formData;
253
    }
254
}
255