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 ( 28b23d...97cb80 )
by Stan
03:33
created

Processor::processEntitiesChain()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 1
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
declare(strict_types=1);
13
14
namespace Teebot\Api\Command;
15
16
use Teebot\Api\Entity\{
17
    EntityInterface,
18
    Error,
19
    Message,
20
    MessageEntity,
21
    MessageEntityArray,
22
    Update
23
};
24
use Teebot\Api\Method\MethodInterface;
25
use Teebot\Api\Exception\{
26
    ProcessEntitiesChainException,
27
    ProcessEntitiesException
28
};
29
use Teebot\Api\{
30
    Command\ValueObject\ChainItem, HttpClient, Request, Response
31
};
32
use Teebot\Configuration\{
33
    AbstractContainer as ConfigContainer,
34
    ContainerInterface,
35
    ValueObject\EventConfig
36
};
37
38
class Processor
39
{
40
    /**
41
     * @var ConfigContainer $config
42
     */
43
    protected $config;
44
45
    /**
46
     * @var HttpClient $httpClient
47
     */
48
    protected $httpClient;
49
50
    /**
51
     * @param ContainerInterface $config
52
     * @param HttpClient      $httpClient
53
     */
54
    public function __construct(ContainerInterface $config, HttpClient $httpClient)
55
    {
56
        $this->config     = $config;
0 ignored issues
show
Documentation Bug introduced by
It seems like $config of type object<Teebot\Configuration\ContainerInterface> is incompatible with the declared type object<AbstractContainer> of property $config.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
57
        $this->httpClient = $httpClient;
0 ignored issues
show
Documentation Bug introduced by
It seems like $httpClient of type object<Teebot\Api\HttpClient> is incompatible with the declared type object<HttpClient> of property $httpClient.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
58
    }
59
60
    /**
61
     * Returns configuration container
62
     *
63
     * @return ContainerInterface
64
     */
65
    public function getConfig(): ContainerInterface
66
    {
67
        return $this->config;
68
    }
69
70
    /**
71
     * Processes array of entities from response object, root entities must be either update entity
72
     * or error entity in case of error response from Telegram's API.
73
     *
74
     * @param array $entities Array of entities (Update or Error) passed either from response object or
75
     *                        directly to the method
76
     *
77
     * @throws ProcessEntitiesException
78
     */
79
    public function processEntities(array $entities)
80
    {
81
        /** @var Update $entity */
82
        foreach ($entities as $entity) {
83
            $entitiesChain = $this->getEntitiesChain($entity);
84
85
            if (empty($entitiesChain)) {
86
                throw new ProcessEntitiesException("Entities chain is empty! There must be an unknown entity passed!");
87
            }
88
89
            $this->processEntitiesChain($entitiesChain);
90
        }
91
    }
92
93
    /**
94
     * Returns entities chain generated from nested sub-entities of the passed entity
95
     *
96
     * Generated hierarchy of the events:
97
     *
98
     * - Error
99
     * - Update
100
     *   - Message
101
     *     - Command
102
     *       - <Command name>
103
     *     - Audio ... <Any supported entity>
104
     *   - Inline Query
105
     *   - Chosen Inline Result
106
     *
107
     * @param EntityInterface $entity Update or Error entity object
108
     *
109
     * @return array
110
     */
111
    public function getEntitiesChain(EntityInterface $entity): array
112
    {
113
        if ($entity instanceof Error) {
114
            return [
115
                new ChainItem($entity),
116
            ];
117
        }
118
        if (!$entity instanceof Update) {
119
            return [];
120
        }
121
122
        $updateTypeEntity = $entity->getUpdateTypeEntity();
123
124
        $events = [
125
            new ChainItem($entity),
126
            new ChainItem($updateTypeEntity, $updateTypeEntity),
127
        ];
128
129
        if ($updateTypeEntity instanceof Message && $updateTypeEntity->getMessageTypeEntity()) {
130
131
            $messageTypeEntity = $updateTypeEntity->getMessageTypeEntity();
132
133
            $events[] = new ChainItem($messageTypeEntity, $updateTypeEntity);
134
135
            if ($messageTypeEntity instanceof MessageEntityArray) {
136
                $entities = $messageTypeEntity->getEntities();
137
138
                foreach ($entities as $entity) {
139
                    $events[] = new ChainItem($entity, $updateTypeEntity);
140
                }
141
            }
142
        }
143
144
        return $events;
145
    }
146
147
    /**
148
     * Processes generated entities chain, if triggered event returns false stops processing
149
     *
150
     * @param array $entitiesChain Array of entities
151
     *
152
     * @throws ProcessEntitiesChainException
153
     */
154
    protected function processEntitiesChain(array $entitiesChain)
155
    {
156
        /** @var ChainItem $chainItem */
157
        foreach ($entitiesChain as $chainItem) {
158
            try {
159
                $continue = $this->triggerEventForEntity($chainItem);
160
161
                if (!$continue) {
162
                    return;
163
                }
164
            } catch (\Exception $e) {
165
                throw new ProcessEntitiesChainException('Processing of the entities chain error', 0, $e);
166
            }
167
        }
168
    }
169
170
    /**
171
     * Triggers desired event and returns boolean result from run method of the event. If run() returns
172
     * false the processing in main process method will be stopped and further events (if any)
173
     * will not be triggered otherwise process will continue until either first false returned or the very
174
     * last event in the flow.
175
     *
176
     * @param ChainItem $chainItem
177
     *
178
     * @return bool
179
     */
180
    protected function triggerEventForEntity(ChainItem $chainItem): bool
181
    {
182
        $entity             = $chainItem->getEntity();
183
        $eventConfiguration = $this->getEventConfiguration($entity);
184
185
        if ($eventConfiguration === null) {
186
            return true;
187
        }
188
189
        $eventClass = $eventConfiguration->getClass();
190
191
        if (class_exists($eventClass)) {
192
            $event = new $eventClass();
193
194
            if (!$event instanceof EventInterface && !$event instanceof CommandInterface) {
195
                return true;
196
            }
197
198
            /** @var AbstractCommand $eventClass */
199
            if ($event instanceof CommandInterface && $entity instanceof MessageEntity && $entity->isNativeCommand()) {
200
                $event->setArgs($entity->getArgs());
201
            }
202
203
            $referencedEntity = $chainItem->getParent() ? $chainItem->getParent() : $entity;
204
205
            $event
206
                ->setProcessor($this)
207
                ->setParams($eventConfiguration->getParams())
208
                ->setEntity($referencedEntity);
209
210
            return (bool) $event->run();
211
        }
212
213
        return true;
214
    }
215
216
    /**
217
     * Returns event configuration item search by the data from entity
218
     *
219
     * @param EntityInterface $entity   Entity for which the corresponding event should be triggered
220
     *                                  be treated as a command
221
     *
222
     * @return null|EventConfig
223
     */
224
    protected function getEventConfiguration(EntityInterface $entity): ?EventConfig
225
    {
226
        $preDefinedEvents = $this->config->get('events');
227
        $entityEventType  = $entity->getEntityType();
228
229
        if (!is_array($preDefinedEvents)) {
230
            return null;
231
        }
232
233
        /** @var EventConfig $preDefinedEvent */
234
        foreach ($preDefinedEvents as $preDefinedEvent) {
235
            $className = null;
236
237
            if ($preDefinedEvent->getType() == Message::MESSAGE_TYPE_REGEXP_COMMAND) {
238
                if ($entity instanceof Message && $entity->hasBuiltinRegexpCommand($preDefinedEvent->getCommand())) {
239
                    $className = $preDefinedEvent->getClass();
240
                }
241
            }
242
243
            if ($preDefinedEvent->getType() == $entityEventType) {
244
                $className = $preDefinedEvent->getClass();
245
246
                if ($entity instanceof MessageEntity && $entity->isNativeCommand()) {
247
                    if (!$this->isCommandSupported($preDefinedEvent->getCommand(), $entity->getCommand())) {
248
                        continue;
249
                    }
250
                }
251
            }
252
253
            if ($className && class_exists($className)) {
254
                return $preDefinedEvent;
255
            }
256
        }
257
258
        return null;
259
    }
260
261
    /**
262
     * Checks whether command is defined in config and matches the current one
263
     *
264
     * @param string|null $preDefinedCommand Pre command
265
     * @param string      $command           Command name
266
     *
267
     * @return bool
268
     */
269
    protected function isCommandSupported(?string $preDefinedCommand, string $command): bool
270
    {
271
        return $preDefinedCommand !== null && strtolower($preDefinedCommand) == strtolower($command);
272
    }
273
274
    /**
275
     * Executes remote method and returns response object
276
     *
277
     * @param MethodInterface $method     Method instance
278
     * @param bool            $silentMode If set to true then the events, mapped (in config or by default)
279
     *                                    to the entities in the result will not be triggered
280
     * @param EntityInterface $parent     Parent entity (if any)
281
     *
282
     * @return Response
283
     */
284
    public function call(MethodInterface $method, $silentMode = false, EntityInterface $parent = null)
285
    {
286
        $request  = new Request($this->httpClient);
287
        $response = $request->exec($method, $parent);
288
289
        return $this->processResponse($response, $silentMode);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $request->exec($method, $parent) on line 287 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...
290
    }
291
292
    /**
293
     * Returns a response object and starts the entities processing (if not in silent mode). Method
294
     * should be used only when webhook is set.
295
     *
296
     * @param string $data       Raw json data either received from php input or passed manually
297
     * @param bool   $silentMode If set to true then the events, mapped (in config or by default)
298
     *                           to the entities in the result will not be triggered
299
     *
300
     * @return Response
301
     */
302
    public function getWebhookResponse($data, $silentMode = false)
303
    {
304
        $response = new Response($data, Update::class);
305
306
        return $this->processResponse($response, $silentMode);
307
    }
308
309
    /**
310
     * Processes entities from the response object if not in silent mode or error is received.
311
     *
312
     * @param Response $response   Response object which includes entities
313
     * @param bool     $silentMode If set to true then the events, mapped (in config or by default)
314
     *                             to the entities in the result will not be triggered
315
     *
316
     * @return Response
317
     */
318
    protected function processResponse(Response $response, $silentMode = false)
319
    {
320
        if (!empty($response->getEntities()) && ($silentMode === false || $response->isErrorReceived())) {
321
            $this->processEntities($response->getEntities());
322
        }
323
324
        return $response;
325
    }
326
}
327