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

TgLog::buildMultipartFormData()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 12
cp 0
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 16
nc 3
nop 3
crap 12
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 16
    public function __construct(string $botToken, LoggerInterface $logger = null, RequestHandlerInterface $handler = null)
74
    {
75 16
        $this->botToken = $botToken;
76
77
        // Initialize new dummy logger (PSR-3 compatible) if not injected
78 16
        if ($logger === null) {
79 16
            $logger = new DummyLogger();
80
        }
81 16
        $this->logger = $logger;
82
83
        // Initialize new Guzzle client if not injected
84 16
        if ($handler === null) {
85 16
            $handler = new GuzzleRequestHandler(null, $logger);
86
        }
87 16
        $this->requestHandler = $handler;
88
89 16
        $this->constructApiUrl();
90 16
    }
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 7
    public function performApiRequest(TelegramMethods $method): TelegramTypes
100
    {
101 7
        $this->logger->debug('Request for API call, resetting internal values', [get_class($method)]);
102 7
        $this->resetObjectValues();
103 7
        $telegramRawData = $this->sendRequestToTelegram($method, $this->constructFormData($method));
104
        if ($telegramRawData->isError()) {
105
            $this->handleOffErrorRequest($telegramRawData);
106
        }
107
108
        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
        {
155
            $deferred->resolve(new TelegramDocument($response));
156
        },
157
            function (\Exception $exception) use ($deferred)
158
            {
159
                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...
160
                    $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...
161
                else
162
                    $deferred->reject($exception);
163
            });
164
    }
165
166
    /**
167
     * Builds up the Telegram API url
168
     * @return TgLog
169
     */
170 16
    final private function constructApiUrl(): TgLog
171
    {
172 16
        $this->apiUrl = 'https://api.telegram.org/bot' . $this->botToken . '/';
173 16
        $this->logger->debug('Built up the API URL');
174 16
        return $this;
175
    }
176
177
	/**
178
	 * This is the method that actually makes the call, which can be easily overwritten so that our unit tests can work
179
	 *
180
	 * @param TelegramMethods $method
181
	 * @param array $formData
182
	 *
183
	 * @return TelegramRawData
184
	 * @throws \Exception
185
	 */
186
    protected function sendRequestToTelegram(TelegramMethods $method, array $formData): TelegramRawData
187
    {
188
        $e = null;
189
        $this->logger->debug('About to perform HTTP call to Telegram\'s API');
190
        try {
191
            $response = $this->requestHandler->post($this->composeApiMethodUrl($method), $formData);
192
            $this->logger->debug('Got response back from Telegram');
193
	        return $response;
194
        } catch (\Exception $e) {
195
            // It can happen that we have a network problem, in such case, we can't do nothing about it, so rethrow
196
            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...
197
                throw $e;
198
            }
199
	        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...
200
        }
201
    }
202
203
    /**
204
     * @param TelegramMethods $method
205
     * @param array $formData
206
     *
207
     * @return PromiseInterface
208
     */
209
    protected function sendAsyncRequestToTelegram(TelegramMethods $method, array $formData): PromiseInterface
210
    {
211
        $this->logger->debug('About to perform async HTTP call to Telegram\'s API');
212
        $deferred = new Deferred();
213
214
        $promise = $this->requestHandler->postAsync($this->composeApiMethodUrl($method), $formData);
215
        $promise->then(function (ResponseInterface $response) use ($deferred)
216
        {
217
            $deferred->resolve(new TelegramRawData((string) $response->getBody()));
218
        },
219
            function (\Exception $exception) use ($deferred)
220
            {
221
                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...
222
                    $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...
223
                else
224
                    $deferred->reject($exception);
225
            });
226
227
        return $deferred->promise();
228
    }
229
230
    /**
231
     * Resets everything to the default values
232
     *
233
     * @return TgLog
234
     */
235 7
    private function resetObjectValues(): TgLog
236
    {
237 7
        $this->formType = 'application/x-www-form-urlencoded';
238 7
        $this->methodName = '';
239
240 7
        return $this;
241
    }
242
243
    /**
244
     * Builds up the form elements to be sent to Telegram
245
     *
246
     * @TODO Move this to apart function
247
     *
248
     * @param TelegramMethods $method
249
     * @return array
250
     * @throws \unreal4u\TelegramAPI\Exceptions\MissingMandatoryField
251
     */
252 7
    private function constructFormData(TelegramMethods $method): array
253
    {
254 7
        $result = $this->checkSpecialConditions($method);
255
256 7
        switch ($this->formType) {
257 7
            case 'application/x-www-form-urlencoded':
258 7
                $this->logger->debug('Creating x-www-form-urlencoded form (AKA fast request)');
259
                $formData = [
260 7
	                'headers' =>  [
261
	                	'Content-Type' => 'application/x-www-form-urlencoded',
262
	                ],
263 7
                    'body' => http_build_query($method->export(), '', '&'),
264
                ];
265 5
                break;
266
            case 'multipart/form-data':
267
                $formData = $this->buildMultipartFormData($method->export(), $result['id'], $result['stream']);
268
                break;
269
            default:
270
                $this->logger->critical(sprintf(
271
                    'Invalid form-type detected, if you incur in such a situation, this is most likely a product to '.
272
                    'a bug. Please copy entire line and report at %s',
273
                    'https://github.com/unreal4u/telegram-api/issues'
274
                ), [
275
                    $this->formType
276
                ]);
277
                $formData = [
278
                	'headers' => [
279
	                    'Content-Type' => $this->formType
280
                    ]
281
                ];
282
                break;
283
        }
284 5
        $this->logger->debug('About to send following data', $formData);
285
286 5
        return $formData;
287
    }
288
289
    /**
290
     * Can perform any special checks needed to be performed before sending the actual request to Telegram
291
     *
292
     * This will return an array with data that will be different in each case (for now). This can be changed in the
293
     * future.
294
     *
295
     * @param TelegramMethods $method
296
     * @return array
297
     */
298 7
    private function checkSpecialConditions(TelegramMethods $method): array
299
    {
300 7
        $this->logger->debug('Checking whether to apply special conditions to this request');
301 7
        $method->performSpecialConditions();
302
303 7
        $return = [false];
304
305 7
        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...
306 6
            if (is_object($value) && $value instanceof InputFile) {
307
                $this->logger->debug('About to send a file, so changing request to use multi-part instead');
308
                // If we are about to send a file, we must use the multipart/form-data way
309
                $this->formType = 'multipart/form-data';
310
                $return = [
311
                    'id' => $key,
312 6
                    'stream' => $value->getStream(),
313
                ];
314
            }
315
        }
316
317 7
        return $return;
318
    }
319
320
    /**
321
     * Builds up the URL with which we can work with
322
     *
323
     * All methods in the Bot API are case-insensitive.
324
     * All queries must be made using UTF-8.
325
     *
326
     * @see https://core.telegram.org/bots/api#making-requests
327
     *
328
     * @param TelegramMethods $call
329
     * @return string
330
     */
331 6
    protected function composeApiMethodUrl(TelegramMethods $call): string
332
    {
333 6
        $completeClassName = get_class($call);
334 6
        $this->methodName = substr($completeClassName, strrpos($completeClassName, '\\') + 1);
335 6
        $this->logger->info('About to perform API request', ['method' => $this->methodName]);
336
337 6
        return $this->apiUrl . $this->methodName;
338
    }
339
340
    /**
341
     * Builds up a multipart form-like array for Guzzle
342
     *
343
     * @param array $data The original object in array form
344
     * @param string $fileKeyName A file handler will be sent instead of a string, state here which field it is
345
     * @param resource $stream The actual file handler
346
     * @return array Returns the actual formdata to be sent
347
     */
348
    private function buildMultipartFormData(array $data, string $fileKeyName, $stream): array
349
    {
350
        $this->logger->debug('Creating multi-part form array data (complex and expensive)');
351
        $formData = [
352
        	'body' => null
353
        ];
354
355
        $multiPartArray = [];
356
        foreach ($data as $id => $value) {
357
            // Always send as a string unless it's a file
358
            $multiPart = [
359
                'name' => $id,
360
                'contents' => null,
361
            ];
362
363
            if ($id === $fileKeyName) {
364
                $multiPart['contents'] = $stream;
365
            } else {
366
                $multiPart['contents'] = (string)$value;
367
            }
368
369
            $multiPartArray[] = $multiPart;
370
        }
371
372
        $formData['body'] = new MultipartStream($multiPartArray);
373
        return $formData;
374
    }
375
376
    /**
377
     * @param TelegramRawData $telegramRawData
378
     * @return TgLog
379
     * @throws CustomClientException
380
     */
381
    private function handleOffErrorRequest(TelegramRawData $telegramRawData): TgLog
382
    {
383
        $errorRequest = new UnsuccessfulRequest($telegramRawData->getErrorData(), $this->logger);
384
385
        $clientException = new CustomClientException(
386
            $errorRequest->description,
387
            $errorRequest->error_code,
388
            $telegramRawData->getException()
389
        );
390
        $clientException->setParameters($errorRequest->parameters);
391
        throw $clientException;
392
    }
393
}