Completed
Pull Request — master (#53)
by Rick
02:38
created

TgLog::performAsyncApiRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace unreal4u\TelegramAPI;
6
7
use GuzzleHttp\Psr7\MultipartStream;
8
use React\Promise\Deferred;
9
use React\Promise\PromiseInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Log\LoggerInterface;
12
use unreal4u\TelegramAPI\Abstracts\TelegramMethods;
13
use unreal4u\TelegramAPI\Abstracts\TelegramTypes;
14
use unreal4u\TelegramAPI\Exceptions\ClientException as CustomClientException;
15
use unreal4u\TelegramAPI\InternalFunctionality\DummyLogger;
16
use unreal4u\TelegramAPI\InternalFunctionality\TelegramDocument;
17
use unreal4u\TelegramAPI\InternalFunctionality\TelegramRawData;
18
use unreal4u\TelegramAPI\Telegram\Types\Custom\InputFile;
19
use unreal4u\TelegramAPI\Telegram\Types\Custom\UnsuccessfulRequest;
20
use unreal4u\TelegramAPI\Telegram\Types\File;
21
22
/**
23
 * The main API which does it all
24
 */
25
class TgLog
26
{
27
    /**
28
     * @var RequestHandlerInterface
29
     */
30
    protected $requestHandler;
31
32
    /**
33
     * Stores the token
34
     * @var string
35
     */
36
    private $botToken;
37
38
    /**
39
     * Contains an instance to a PSR-3 compatible logger
40
     * @var LoggerInterface
41
     */
42
    protected $logger;
43
44
    /**
45
     * Stores the API URL from Telegram
46
     * @var string
47
     */
48
    private $apiUrl = '';
49
50
    /**
51
     * With this flag we'll know what type of request to send to Telegram
52
     *
53
     * 'application/x-www-form-urlencoded' is the "normal" one, which is simpler and quicker.
54
     * 'multipart/form-data' should be used only when you upload documents, photos, etc.
55
     *
56
     * @var string
57
     */
58
    private $formType = 'application/x-www-form-urlencoded';
59
60
    /**
61
     * Stores the last method name used
62
     * @var string
63
     */
64
    protected $methodName = '';
65
66
    /**
67
     * TelegramLog constructor.
68
     *
69
     * @param string $botToken
70
     * @param LoggerInterface $logger
71
     * @param RequestHandlerInterface $handler
72
     */
73 40
    public function __construct(string $botToken, LoggerInterface $logger = null, RequestHandlerInterface $handler = null)
74
    {
75 40
        $this->botToken = $botToken;
76
77
        // Initialize new dummy logger (PSR-3 compatible) if not injected
78 40
        if ($logger === null) {
79 40
            $logger = new DummyLogger();
80
        }
81 40
        $this->logger = $logger;
82
83
        // Initialize new Guzzle client if not injected
84 40
        if ($handler === null) {
85 40
            $handler = new GuzzleRequestHandler(null, $logger);
86
        }
87 40
        $this->requestHandler = $handler;
88
89 40
        $this->constructApiUrl();
90 40
    }
91
92
    /**
93
     * Prepares and sends an API request to Telegram
94
     *
95
     * @param TelegramMethods $method
96
     * @return TelegramTypes
97
     * @throws \unreal4u\TelegramAPI\Exceptions\MissingMandatoryField
98
     */
99 31
    public function performApiRequest(TelegramMethods $method): TelegramTypes
100
    {
101 31
        $this->logger->debug('Request for API call, resetting internal values', [get_class($method)]);
102 31
        $this->resetObjectValues();
103 31
        $telegramRawData = $this->sendRequestToTelegram($method, $this->constructFormData($method));
104 24
        if ($telegramRawData->isError()) {
105
            $this->handleOffErrorRequest($telegramRawData);
106
        }
107
108 24
        return $method::bindToObject($telegramRawData, $this->logger);
109
    }
110
111
    /**
112
     * @param TelegramMethods $method
113
     *
114
     * @return PromiseInterface
115
     */
116
    public function performAsyncApiRequest(TelegramMethods $method)
117
    {
118
        $this->logger->debug('Request for async API call, resetting internal values', [get_class($method)]);
119
        $this->resetObjectValues();
120
        return $this->sendAsyncRequestToTelegram($method, $this->constructFormData($method));
121
    }
122
123
    /**
124
     * Will download a file from the Telegram server. Before calling this function, you have to call the getFile method!
125
     *
126
     * @see \unreal4u\TelegramAPI\Telegram\Types\File
127
     * @see \unreal4u\TelegramAPI\Telegram\Methods\GetFile
128
     *
129
     * @param File $file
130
     * @return TelegramDocument
131
     */
132
    public function downloadFile(File $file): TelegramDocument
133
    {
134
        $this->logger->debug('Downloading file from Telegram, creating URL');
135
        $url = 'https://api.telegram.org/file/bot' . $this->botToken . '/' . $file->file_path;
136
        $this->logger->debug('About to perform request to begin downloading file');
137
        return new TelegramDocument($this->requestHandler->get($url));
138
    }
139
140
    /**
141
     * @param File $file
142
     *
143
     * @return PromiseInterface
144
     */
145
    public function downloadFileAsync(File $file): PromiseInterface
146
    {
147
        $this->logger->debug('Downloading file async from Telegram, creating URL');
148
        $url = 'https://api.telegram.org/file/bot' . $this->botToken . '/' . $file->file_path;
149
        $this->logger->debug('About to perform request to begin downloading file');
150
151
        $deferred = new Deferred();
152
153
        return $this->requestHandler->getAsync($url)->then(function (ResponseInterface $response) use ($deferred) {
154
            $deferred->resolve(new TelegramDocument($response));
155
        },
156
        function (\Exception $exception) use ($deferred) {
157
            if (method_exists($exception, 'getResponse') && !empty($exception->getResponse()->getBody())) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getResponse() does only exist in the following sub-classes of Exception: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
158
                $deferred->resolve(new TelegramDocument($exception->getResponse()));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getResponse() does only exist in the following sub-classes of Exception: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
159
            } else {
160
                    $deferred->reject($exception);
161
            }
162
        });
163
    }
164
165
    /**
166
     * Builds up the Telegram API url
167
     * @return TgLog
168
     */
169 40
    final private function constructApiUrl(): TgLog
170
    {
171 40
        $this->apiUrl = 'https://api.telegram.org/bot' . $this->botToken . '/';
172 40
        $this->logger->debug('Built up the API URL');
173 40
        return $this;
174
    }
175
176
    /**
177
     * This is the method that actually makes the call, which can be easily overwritten so that our unit tests can work
178
     *
179
     * @param TelegramMethods $method
180
     * @param array $formData
181
     *
182
     * @return TelegramRawData
183
     * @throws \Exception
184
     */
185 24
    protected function sendRequestToTelegram(TelegramMethods $method, array $formData): TelegramRawData
186
    {
187 24
        $e = null;
188 24
        $this->logger->debug('About to perform HTTP call to Telegram\'s API');
189
        try {
190 24
            $response = $this->requestHandler->post($this->composeApiMethodUrl($method), $formData);
191 24
            $this->logger->debug('Got response back from Telegram');
192 24
            return $response;
193
        } catch (\Exception $e) {
194
            // It can happen that we have a network problem, in such case, we can't do nothing about it, so rethrow
195
            if (!method_exists($e, 'getResponse') || empty($e->getResponse())) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getResponse() does only exist in the following sub-classes of Exception: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
196
                throw $e;
197
            }
198
            return new TelegramRawData((string)$e->getResponse()->getBody(), $e);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getResponse() does only exist in the following sub-classes of Exception: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
199
        }
200
    }
201
202
    /**
203
     * @param TelegramMethods $method
204
     * @param array $formData
205
     *
206
     * @return PromiseInterface
207
     */
208
    protected function sendAsyncRequestToTelegram(TelegramMethods $method, array $formData): PromiseInterface
209
    {
210
        $this->logger->debug('About to perform async HTTP call to Telegram\'s API');
211
        $deferred = new Deferred();
212
213
        $promise = $this->requestHandler->postAsync($this->composeApiMethodUrl($method), $formData);
214
        $promise->then(function (ResponseInterface $response) use ($deferred)
215
        {
216
            $deferred->resolve(new TelegramRawData((string) $response->getBody()));
217
        },
218
        function (\Exception $exception) use ($deferred) {
219
            if (method_exists($exception, 'getResponse') && !empty($exception->getResponse()->getBody())) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getResponse() does only exist in the following sub-classes of Exception: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
220
                $deferred->resolve(new TelegramRawData((string) $exception->getResponse()->getBody(), $exception));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getResponse() does only exist in the following sub-classes of Exception: GuzzleHttp\Exception\BadResponseException, GuzzleHttp\Exception\ClientException, GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException, GuzzleHttp\Exception\ServerException, GuzzleHttp\Exception\TooManyRedirectsException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
221
            } else {
222
                $deferred->reject($exception);
223
            }
224
        });
225
226
        return $deferred->promise();
227
    }
228
229
    /**
230
     * Resets everything to the default values
231
     *
232
     * @return TgLog
233
     */
234 31
    private function resetObjectValues(): TgLog
235
    {
236 31
        $this->formType = 'application/x-www-form-urlencoded';
237 31
        $this->methodName = '';
238
239 31
        return $this;
240
    }
241
242
    /**
243
     * Builds up the form elements to be sent to Telegram
244
     *
245
     * @TODO Move this to apart function
246
     *
247
     * @param TelegramMethods $method
248
     * @return array
249
     * @throws \unreal4u\TelegramAPI\Exceptions\MissingMandatoryField
250
     */
251 31
    private function constructFormData(TelegramMethods $method): array
252
    {
253 31
        $result = $this->checkSpecialConditions($method);
254
255 31
        switch ($this->formType) {
256 31
            case 'application/x-www-form-urlencoded':
257 27
                $this->logger->debug('Creating x-www-form-urlencoded form (AKA fast request)');
258
                $formData = [
259 27
                    'headers' =>  [
260
                        'Content-Type' => 'application/x-www-form-urlencoded',
261
                    ],
262 27
                    'body' => http_build_query($method->export(), '', '&'),
263
                ];
264 25
                break;
265 4
            case 'multipart/form-data':
266 4
                $formData = $this->buildMultipartFormData($method->export(), $result['id'], $result['stream']);
267 4
                break;
268
            default:
269
                $this->logger->critical(sprintf(
270
                    'Invalid form-type detected, if you incur in such a situation, this is most likely a product to '.
271
                    'a bug. Please copy entire line and report at %s',
272
                    'https://github.com/unreal4u/telegram-api/issues'
273
                ), [
274
                    $this->formType
275
                ]);
276
                $formData = [
277
                    'headers' => [
278
                        'Content-Type' => $this->formType
279
                    ]
280
                ];
281
                break;
282
        }
283 29
        $this->logger->debug('About to send following data', $formData);
284
285 29
        return $formData;
286
    }
287
288
    /**
289
     * Can perform any special checks needed to be performed before sending the actual request to Telegram
290
     *
291
     * This will return an array with data that will be different in each case (for now). This can be changed in the
292
     * future.
293
     *
294
     * @param TelegramMethods $method
295
     * @return array
296
     */
297 31
    private function checkSpecialConditions(TelegramMethods $method): array
298
    {
299 31
        $this->logger->debug('Checking whether to apply special conditions to this request');
300 31
        $method->performSpecialConditions();
301
302 31
        $return = [false];
303
304 31
        foreach ($method as $key => $value) {
0 ignored issues
show
Bug introduced by
The expression $method of type object<unreal4u\Telegram...tracts\TelegramMethods> is not traversable.
Loading history...
305 27
            if (is_object($value) && $value instanceof InputFile) {
306 4
                $this->logger->debug('About to send a file, so changing request to use multi-part instead');
307
                // If we are about to send a file, we must use the multipart/form-data way
308 4
                $this->formType = 'multipart/form-data';
309
                $return = [
310 4
                    'id' => $key,
311 27
                    'stream' => $value->getStream(),
312
                ];
313
            }
314
        }
315
316 31
        return $return;
317
    }
318
319
    /**
320
     * Builds up the URL with which we can work with
321
     *
322
     * All methods in the Bot API are case-insensitive.
323
     * All queries must be made using UTF-8.
324
     *
325
     * @see https://core.telegram.org/bots/api#making-requests
326
     *
327
     * @param TelegramMethods $call
328
     * @return string
329
     */
330 30
    protected function composeApiMethodUrl(TelegramMethods $call): string
331
    {
332 30
        $completeClassName = get_class($call);
333 30
        $this->methodName = substr($completeClassName, strrpos($completeClassName, '\\') + 1);
334 30
        $this->logger->info('About to perform API request', ['method' => $this->methodName]);
335
336 30
        return $this->apiUrl . $this->methodName;
337
    }
338
339
    /**
340
     * Builds up a multipart form-like array for Guzzle
341
     *
342
     * @param array $data The original object in array form
343
     * @param string $fileKeyName A file handler will be sent instead of a string, state here which field it is
344
     * @param resource $stream The actual file handler
345
     * @return array Returns the actual formdata to be sent
346
     */
347 4
    private function buildMultipartFormData(array $data, string $fileKeyName, $stream): array
348
    {
349 4
        $this->logger->debug('Creating multi-part form array data (complex and expensive)');
350
        $formData = [
351 4
            'body' => null
352
        ];
353
354 4
        $multiPartArray = [];
355 4
        foreach ($data as $id => $value) {
356
            // Always send as a string unless it's a file
357
            $multiPart = [
358 4
                'name' => $id,
359
                'contents' => null,
360
            ];
361
362 4
            if ($id === $fileKeyName) {
363 4
                $multiPart['contents'] = $stream;
364
            } else {
365 4
                $multiPart['contents'] = (string)$value;
366
            }
367
368 4
            $multiPartArray[] = $multiPart;
369
        }
370
371 4
        $formData['body'] = new MultipartStream($multiPartArray);
372 4
        return $formData;
373
    }
374
375
    /**
376
     * @param TelegramRawData $telegramRawData
377
     * @return TgLog
378
     * @throws CustomClientException
379
     */
380
    private function handleOffErrorRequest(TelegramRawData $telegramRawData): TgLog
381
    {
382
        $errorRequest = new UnsuccessfulRequest($telegramRawData->getErrorData(), $this->logger);
383
384
        $clientException = new CustomClientException(
385
            $errorRequest->description,
386
            $errorRequest->error_code,
387
            $telegramRawData->getException()
388
        );
389
        $clientException->setParameters($errorRequest->parameters);
390
        throw $clientException;
391
    }
392
}