Completed
Push — master ( 0cb834...ce1742 )
by Camilo
04:29
created

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