GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 5ac723...c719eb )
by Stan
07:02
created

Processor::processResponse()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.2
c 0
b 0
f 0
cc 4
eloc 4
nc 2
nop 2
1
<?php
2
3
/**
4
 * Event and command executor class. Goes trough entities in response object and triggers corresponding
5
 * entity events and/or commands, either mapped in config file or via default Teebot\Bot namespace.
6
 *
7
 * @package Teebot (Telegram bot framework)
8
 *
9
 * @author  Stanislav Drozdov <[email protected]>
10
 */
11
12
namespace Teebot\Api\Command;
13
14
use Teebot\Api\Entity\EntityInterface;
15
use Teebot\Api\Entity\Error;
16
use Teebot\Api\Entity\Message;
17
use Teebot\Api\Entity\MessageEntity;
18
use Teebot\Api\Entity\MessageEntityArray;
19
use Teebot\Api\Entity\Update;
20
use Teebot\Api\Exception\ProcessEntitiesChainException;
21
use Teebot\Api\Exception\ProcessEntitiesException;
22
use Teebot\Api\HttpClient;
23
use Teebot\Api\Method\AbstractMethod;
24
use Teebot\Api\Request;
25
use Teebot\Api\Response;
26
use Teebot\Configuration\AbstractContainer as ConfigContainer;
27
use Teebot\Configuration\ValueObject\EventConfig;
28
29
class Processor
30
{
31
    /** @var ConfigContainer $config */
32
    protected $config;
33
34
    /** @var HttpClient $httpClient */
35
    protected $httpClient;
36
37
    /**
38
     * Processor constructor.
39
     *
40
     * @param ConfigContainer $config
41
     * @param HttpClient      $httpClient
42
     */
43
    public function __construct(ConfigContainer $config, HttpClient $httpClient)
44
    {
45
        $this->config     = $config;
46
        $this->httpClient = $httpClient;
47
    }
48
49
    /**
50
     * Returns configuration container
51
     *
52
     * @return ConfigContainer
53
     */
54
    public function getConfig()
55
    {
56
        return $this->config;
57
    }
58
59
    /**
60
     * Processes array of entities from response object, root entities must be either update entity
61
     * or error entity in case of error response from Telegram's API.
62
     *
63
     * @param array $entities Array of entities (Update or Error) passed either from response object or
64
     *                        directly to the method
65
     *
66
     * @return bool
67
     *
68
     * @throws ProcessEntitiesException
69
     */
70
    public function processEntities(array $entities)
71
    {
72
        /** @var Update $entity */
73
        foreach ($entities as $entity) {
74
            $entitiesChain = $this->getEntitiesChain($entity);
75
76
            if (empty($entitiesChain)) {
77
                throw new ProcessEntitiesException("Unknown entity! Skipping.");
78
            }
79
80
            $result = $this->processEntitiesChain($entitiesChain);
81
82
            if ($result == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
83
                throw new ProcessEntitiesException("Failed to process the entity!");
84
            }
85
        }
86
87
        return true;
88
    }
89
90
    /**
91
     * Returns entities chain generated from nested sub-entities of the passed entity
92
     *
93
     * Generated hierarchy of the events:
94
     *
95
     * - Error
96
     * - Update
97
     *   - Message
98
     *     - Command
99
     *       - <Command name>
100
     *     - Audio ... <Any supported entity>
101
     *   - Inline Query
102
     *   - Chosen Inline Result
103
     *
104
     * @param Update|Error $entity Update or Error entity object
105
     *
106
     * @return array
107
     */
108
    public function getEntitiesChain($entity)
109
    {
110
        if ($entity instanceof Error) {
111
            return [
112
                ['entity' => $entity],
113
            ];
114
        }
115
        if (!$entity instanceof Update) {
116
            return [];
117
        }
118
119
        $updateTypeEntity = $entity->getUpdateTypeEntity();
120
121
        $events = [
122
            ['entity' => $entity],
123
            ['entity' => $updateTypeEntity, 'parent' => $updateTypeEntity],
124
        ];
125
126
        if ($updateTypeEntity instanceof Message && $updateTypeEntity->getMessageTypeEntity()) {
127
128
            $messageTypeEntity = $updateTypeEntity->getMessageTypeEntity();
129
130
            $events[] = [
131
                'entity' => $messageTypeEntity,
132
                'parent' => $updateTypeEntity,
133
            ];
134
135
            if ($messageTypeEntity instanceof MessageEntityArray) {
136
                $entities = $messageTypeEntity->getEntities();
137
138
                foreach ($entities as $entity) {
139
                    $events[] = [
140
                        'entity' => $entity,
141
                        'parent' => $updateTypeEntity,
142
                    ];
143
                }
144
            }
145
        }
146
147
        return $events;
148
    }
149
150
    /**
151
     * Processes generated entities chain, if triggered event returns false stops processing
152
     *
153
     * @param array $entitiesChain Array of entities
154
     *
155
     * @throws ProcessEntitiesChainException
156
     *
157
     * @return bool
158
     */
159
    protected function processEntitiesChain(array $entitiesChain)
160
    {
161
        foreach ($entitiesChain as $entityData) {
162
            try {
163
                $parent   = isset($entityData['parent']) ? $entityData['parent'] : null;
164
                $continue = $this->triggerEventForEntity($entityData['entity'], $parent);
165
166
                if (!$continue) {
167
                    return true;
168
                }
169
            } catch (\Exception $e) {
170
                throw new ProcessEntitiesChainException('Process entities chain error', 0, $e);
171
            }
172
        }
173
174
        return true;
175
    }
176
177
    /**
178
     * Triggers desired event and returns boolean result from run method of the event. If run() returns
179
     * false the processing in main process method will be stopped and further events (if any)
180
     * will not be triggered otherwise process will continue until either first false returned or the very
181
     * last event in the flow.
182
     *
183
     * @param EntityInterface $entity Entity for which the corresponding event should be triggered
184
     * @param EntityInterface $parent Entity's parent if any
185
     *
186
     * @return bool
187
     */
188
    protected function triggerEventForEntity(EntityInterface $entity, EntityInterface $parent = null)
189
    {
190
        $eventConfiguration = $this->getEventConfiguration($entity);
191
192
        if ($eventConfiguration === null) {
193
            return true;
194
        }
195
196
        $eventClass = $eventConfiguration->getClass();
197
198
        if (class_exists($eventClass)) {
199
            $event = new $eventClass();
200
201
            if (!$event instanceof EventInterface && !$event instanceof CommandInterface) {
202
                return true;
203
            }
204
205
            /** @var AbstractCommand $eventClass */
206
            if ($event instanceof CommandInterface && $entity instanceof MessageEntity && $entity->isNativeCommand()) {
207
                $event->setArgs($entity->getArgs());
208
            }
209
210
            $referencedEntity = $parent ? $parent : $entity;
211
212
            $event
213
                ->setProcessor($this)
214
                ->setParams($eventConfiguration->getParams())
215
                ->setEntity($referencedEntity);
216
217
            return $event->run();
218
        }
219
220
        return true;
221
    }
222
223
    /**
224
     * Returns event configuration item search by the data from entity
225
     *
226
     * @param EntityInterface $entity   Entity for which the corresponding event should be triggered
227
     *                                  be treated as a command
228
     *
229
     * @return null|EventConfig
230
     */
231
    protected function getEventConfiguration(EntityInterface $entity)
232
    {
233
        $preDefinedEvents = $this->config->get('events');
234
        $entityEventType  = $entity->getEntityType();
235
236
        if (!is_array($preDefinedEvents)) {
237
            return null;
238
        }
239
240
        /** @var EventConfig $preDefinedEvent */
241
        foreach ($preDefinedEvents as $preDefinedEvent) {
242
            $className = null;
243
244
            if ($preDefinedEvent->getType() == Message::MESSAGE_TYPE_REGEXP_COMMAND) {
245
                if ($entity instanceof Message && $entity->hasBuiltinRegexpCommand($preDefinedEvent->getCommand())) {
246
                    $className = $preDefinedEvent->getClass();
247
                }
248
            }
249
250
            if ($preDefinedEvent->getType() == $entityEventType) {
251
                $className = $preDefinedEvent->getClass();
252
253
                if ($entity instanceof MessageEntity && $entity->isNativeCommand()) {
254
                    if (!$this->isCommandSupported($preDefinedEvent->getCommand(), $entity->getCommand())) {
255
                        continue;
256
                    }
257
                }
258
            }
259
260
            if ($className && class_exists($className)) {
261
                return $preDefinedEvent;
262
            }
263
        }
264
265
        return null;
266
    }
267
268
    /**
269
     * Checks whether command is defined in config and matches the current one
270
     *
271
     * @param string|null $preDefinedCommand Pre command
272
     * @param string      $command           Command name
273
     *
274
     * @return bool
275
     */
276
    protected function isCommandSupported($preDefinedCommand, $command)
277
    {
278
        return $preDefinedCommand !== null && strtolower($preDefinedCommand) == strtolower($command);
279
    }
280
281
    /**
282
     * Executes remote method and returns response object
283
     *
284
     * @param AbstractMethod  $method     Method instance
285
     * @param bool            $silentMode If set to true then the events, mapped (in config or by default)
286
     *                                    to the entities in the result will not be triggered
287
     * @param EntityInterface $parent     Parent entity (if any)
288
     *
289
     * @return Response
290
     */
291
    public function call(AbstractMethod $method, $silentMode = false, $parent = null)
292
    {
293
        $request  = new Request($this->httpClient);
294
        $response = $request->exec($method, $parent);
0 ignored issues
show
Bug introduced by
It seems like $parent defined by parameter $parent on line 291 can also be of type object<Teebot\Api\Entity\EntityInterface>; however, Teebot\Api\Request::exec() does only seem to accept null|object<Teebot\Api\Entity\AbstractEntity>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
295
296
        return $this->processResponse($response, $silentMode);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $request->exec($method, $parent) on line 294 can be null; however, Teebot\Api\Command\Processor::processResponse() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
297
    }
298
299
    /**
300
     * Returns a response object and starts the entities processing (if not in silent mode). Method
301
     * should be used only when webhook is set.
302
     *
303
     * @param string $data       Raw json data either received from php input or passed manually
304
     * @param bool   $silentMode If set to true then the events, mapped (in config or by default)
305
     *                           to the entities in the result will not be triggered
306
     *
307
     * @return Response
308
     */
309
    public function getWebhookResponse($data, $silentMode = false)
310
    {
311
        $response = new Response($data, Update::class);
312
313
        return $this->processResponse($response, $silentMode);
314
    }
315
316
    /**
317
     * Processes entities from the response object if not in silent mode or error is received.
318
     *
319
     * @param Response $response   Response object which includes entities
320
     * @param bool     $silentMode If set to true then the events, mapped (in config or by default)
321
     *                             to the entities in the result will not be triggered
322
     *
323
     * @return Response
324
     */
325
    protected function processResponse(Response $response, $silentMode = false)
326
    {
327
        if (!empty($response->getEntities()) && ($silentMode === false || $response->isErrorReceived())) {
328
            $this->processEntities($response->getEntities());
329
        }
330
331
        return $response;
332
    }
333
}
334