rudestan /
Teebot
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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\Command; |
||
| 13 | |||
| 14 | use Teebot\Entity\Command; |
||
| 15 | use Teebot\Entity\Error; |
||
| 16 | use Teebot\Entity\Update; |
||
| 17 | use Teebot\Exception\Notice; |
||
| 18 | use Teebot\Exception\Output; |
||
| 19 | use Teebot\Method\AbstractMethod; |
||
| 20 | use Teebot\Request; |
||
| 21 | use Teebot\Entity\AbstractEntity; |
||
| 22 | use Teebot\Entity\Message; |
||
| 23 | use Teebot\Config; |
||
| 24 | use Teebot\Response; |
||
| 25 | |||
| 26 | class Executor |
||
| 27 | { |
||
| 28 | /** @var Executor */ |
||
| 29 | protected static $instance; |
||
| 30 | |||
| 31 | /** @var Config $config */ |
||
| 32 | protected $config; |
||
| 33 | |||
| 34 | /** @var Request $request */ |
||
| 35 | protected $request; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * Returns a singletone instance of executor |
||
| 39 | * |
||
| 40 | * @return Executor |
||
| 41 | */ |
||
| 42 | public static function getInstance() |
||
| 43 | { |
||
| 44 | if (!self::$instance instanceof self) { |
||
| 45 | self::$instance = new self(); |
||
| 46 | } |
||
| 47 | |||
| 48 | return self::$instance; |
||
| 49 | } |
||
| 50 | |||
| 51 | /** |
||
| 52 | * Returns an instance of config class |
||
| 53 | * |
||
| 54 | * @return Config |
||
| 55 | */ |
||
| 56 | public function getConfig() |
||
| 57 | { |
||
| 58 | return $this->config; |
||
| 59 | } |
||
| 60 | |||
| 61 | /** |
||
| 62 | * Initialises the executor, creates request object |
||
| 63 | * |
||
| 64 | * @param Config $config Configuration object |
||
| 65 | */ |
||
| 66 | public function initWithConfig(Config $config) |
||
| 67 | { |
||
| 68 | $this->config = $config; |
||
| 69 | $this->request = new Request($config); |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Processes array of entities from response object, root entities must be either update entity |
||
| 74 | * or error entity in case of error response from Telegram's API. |
||
| 75 | * |
||
| 76 | * @param array $entities Array of entities (Update or Error) passed either from response object or |
||
| 77 | * directly to the method |
||
| 78 | * |
||
| 79 | * @return bool |
||
| 80 | * |
||
| 81 | * @throws Notice |
||
| 82 | */ |
||
| 83 | public function processEntities(array $entities) |
||
| 84 | { |
||
| 85 | /** @var Update $entity */ |
||
| 86 | foreach ($entities as $entity) { |
||
| 87 | $entitiesFlow = $this->getEntitiesFlow($entity); |
||
| 88 | |||
| 89 | if (empty($entitiesFlow)) { |
||
| 90 | throw new Notice("Unknown entity! Skipping."); |
||
| 91 | } |
||
| 92 | |||
| 93 | $result = $this->processEntitiesFlow($entitiesFlow); |
||
| 94 | |||
| 95 | if ($result == false) { |
||
|
0 ignored issues
–
show
|
|||
| 96 | throw new Notice("Failed to process the entity!"); |
||
| 97 | } |
||
| 98 | } |
||
| 99 | |||
| 100 | return true; |
||
| 101 | } |
||
| 102 | |||
| 103 | /** |
||
| 104 | * Returns flow generated from nested sub-entities of the passed entity |
||
| 105 | * |
||
| 106 | * Generated hierarchy of the events: |
||
| 107 | * |
||
| 108 | * - Error |
||
| 109 | * - Update |
||
| 110 | * - Message |
||
| 111 | * - Command |
||
| 112 | * - <Command name> |
||
| 113 | * - Audio ... <Any supported entity> |
||
| 114 | * - Inline Query |
||
| 115 | * - Chosen Inline Result |
||
| 116 | * |
||
| 117 | * @param Update|Error $entity Update or Error entity object |
||
| 118 | * |
||
| 119 | * @return array |
||
| 120 | */ |
||
| 121 | protected function getEntitiesFlow($entity) |
||
| 122 | { |
||
| 123 | if ($entity instanceof Error) { |
||
| 124 | return [ |
||
| 125 | ['entity' => $entity] |
||
| 126 | ]; |
||
| 127 | } |
||
| 128 | if (!$entity instanceof Update) { |
||
| 129 | return []; |
||
| 130 | } |
||
| 131 | |||
| 132 | $updateTypeEntity = $entity->getUpdateTypeEntity(); |
||
| 133 | |||
| 134 | $events = [ |
||
| 135 | ['entity' => $entity], |
||
| 136 | ['entity' => $updateTypeEntity, 'parent' => $updateTypeEntity], |
||
| 137 | ]; |
||
| 138 | |||
| 139 | if ($updateTypeEntity instanceof Message && $updateTypeEntity->getMessageTypeEntity()) { |
||
| 140 | |||
| 141 | $messageTypeEntity = $updateTypeEntity->getMessageTypeEntity(); |
||
| 142 | |||
| 143 | $events[] = [ |
||
| 144 | 'entity' => $messageTypeEntity, |
||
| 145 | 'parent' => $updateTypeEntity |
||
| 146 | ]; |
||
| 147 | |||
| 148 | if ($messageTypeEntity instanceof Command) { |
||
| 149 | $events[] = [ |
||
| 150 | 'entity' => $messageTypeEntity, |
||
| 151 | 'parent' => $updateTypeEntity, |
||
| 152 | 'is_command' => true |
||
| 153 | ]; |
||
| 154 | } |
||
| 155 | } |
||
| 156 | |||
| 157 | return $events; |
||
| 158 | } |
||
| 159 | |||
| 160 | /** |
||
| 161 | * Processes generated entities flow, if triggered event returns false stops processing |
||
| 162 | * |
||
| 163 | * @param array $entitiesFlow Array of entities flow |
||
| 164 | * |
||
| 165 | * @throws Notice |
||
| 166 | * |
||
| 167 | * @return bool |
||
| 168 | */ |
||
| 169 | protected function processEntitiesFlow(array $entitiesFlow) |
||
| 170 | { |
||
| 171 | foreach ($entitiesFlow as $entityData) { |
||
| 172 | |||
| 173 | try { |
||
| 174 | $continue = $this->triggerEvent( |
||
| 175 | $entityData['entity'], |
||
| 176 | $entityData['parent'] ?? null, |
||
| 177 | $entityData['is_command'] ?? false |
||
| 178 | ); |
||
| 179 | |||
| 180 | if (!$continue) { |
||
| 181 | return true; |
||
| 182 | } |
||
| 183 | } catch (\Exception $e) { |
||
| 184 | Output::log($e); |
||
| 185 | } |
||
| 186 | } |
||
| 187 | |||
| 188 | return true; |
||
| 189 | } |
||
| 190 | |||
| 191 | /** |
||
| 192 | * Triggers desired event and returns boolean result from run method of the event. If run() returns |
||
| 193 | * false the processing in main process method will be stopped and further events (if any) |
||
| 194 | * will not be triggered otherwise process will continue until either first false returned or the very |
||
| 195 | * last event in the flow. |
||
| 196 | * |
||
| 197 | * @param AbstractEntity $entity Entity for which the corresponding event should be triggered |
||
| 198 | * @param AbstractEntity $parent Entity's parent if any |
||
| 199 | * @param bool $isCommand Boolean flag which indicates that passed entity should |
||
| 200 | * be treated as a command |
||
| 201 | * |
||
| 202 | * @return bool |
||
| 203 | */ |
||
| 204 | protected function triggerEvent(AbstractEntity $entity, AbstractEntity $parent = null, $isCommand = false) |
||
| 205 | { |
||
| 206 | if ($this->config->getEvents()) { |
||
| 207 | $eventClassName = $this->getMappedEventClass($entity, $isCommand); |
||
| 208 | } else { |
||
| 209 | $eventClassName = $this->getDefaultEventClass($entity, $isCommand); |
||
| 210 | } |
||
| 211 | |||
| 212 | if (class_exists($eventClassName)) { |
||
| 213 | $eventClass = new $eventClassName(); |
||
| 214 | |||
| 215 | if (!$eventClass instanceof AbstractEntityEvent && !$eventClass instanceof AbstractCommand) { |
||
| 216 | return true; |
||
| 217 | } |
||
| 218 | |||
| 219 | /** @var AbstractCommand $eventClass */ |
||
| 220 | if ($eventClassName instanceof AbstractCommand && $entity instanceof Command) { |
||
| 221 | $eventClass->setArgs($entity->getArgs()); |
||
| 222 | } |
||
| 223 | |||
| 224 | $eventClass->setEntity($parent ?? $entity); |
||
| 225 | |||
| 226 | return $eventClass->run(); |
||
| 227 | } |
||
| 228 | |||
| 229 | return true; |
||
| 230 | } |
||
| 231 | |||
| 232 | /** |
||
| 233 | * Returns mapped event class from the configuration (if it was previously defined) |
||
| 234 | * |
||
| 235 | * @param AbstractEntity $entity Entity for which the corresponding event should be triggered |
||
| 236 | * @param bool $isCommand Boolean flag which indicates that passed entity should |
||
| 237 | * be treated as a command |
||
| 238 | * @return null|string |
||
| 239 | */ |
||
| 240 | protected function getMappedEventClass(AbstractEntity $entity, $isCommand) |
||
| 241 | { |
||
| 242 | $preDefinedEvents = $this->config->getEvents(); |
||
| 243 | $entityEventType = $entity->getEntityType(); |
||
| 244 | |||
| 245 | foreach ($preDefinedEvents as $preDefinedEvent) { |
||
| 246 | if ($preDefinedEvent['type'] == $entityEventType) { |
||
| 247 | $className = $preDefinedEvent['class']; |
||
| 248 | |||
| 249 | if ($entity instanceof Command && $isCommand === true) { |
||
| 250 | $command = $entity->getName(); |
||
| 251 | |||
| 252 | if (isset($preDefinedEvent['command']) && $preDefinedEvent['command'] != $command) { |
||
| 253 | continue; |
||
| 254 | } |
||
| 255 | } |
||
| 256 | |||
| 257 | if (class_exists($className)) { |
||
| 258 | return $className; |
||
| 259 | } |
||
| 260 | } |
||
| 261 | } |
||
| 262 | |||
| 263 | return null; |
||
| 264 | } |
||
| 265 | |||
| 266 | /** |
||
| 267 | * Returns default event class |
||
| 268 | * |
||
| 269 | * @param AbstractEntity $entity Entity for which the corresponding event should be triggered |
||
| 270 | * @param bool $isCommand Boolean flag which indicates that passed entity should |
||
| 271 | * be treated as a command |
||
| 272 | * |
||
| 273 | * @return null|string |
||
| 274 | */ |
||
| 275 | protected function getDefaultEventClass($entity, $isCommand) |
||
| 276 | { |
||
| 277 | $className = null; |
||
| 278 | |||
| 279 | if ($entity instanceof Command && $isCommand === true) { |
||
| 280 | $ucName = ucwords($entity->getName(), Command::PARTS_DELIMITER); |
||
| 281 | $name = str_replace(Command::PARTS_DELIMITER, '', $ucName); |
||
| 282 | |||
| 283 | $className = $this->config->getCommandNamespace() . "\\" . $name; |
||
| 284 | } elseif ($entity instanceof AbstractEntity) { |
||
| 285 | $className = $this->config->getEntityEventNamespace() . "\\". $entity->getEntityType(); |
||
| 286 | } |
||
| 287 | |||
| 288 | if ($className && class_exists($className)) { |
||
| 289 | return $className; |
||
| 290 | } |
||
| 291 | |||
| 292 | return null; |
||
| 293 | } |
||
| 294 | |||
| 295 | /** |
||
| 296 | * Executes remote method and returns response object |
||
| 297 | * |
||
| 298 | * @param AbstractMethod $method Method instance |
||
| 299 | * @param bool $silentMode If set to true then the events, mapped (in config or by default) |
||
| 300 | * to the entities in the result will not be triggered |
||
| 301 | * @param AbstractEntity $parent Parent entity (if any) |
||
| 302 | * |
||
| 303 | * @return Response |
||
| 304 | */ |
||
| 305 | public function callRemoteMethod(AbstractMethod $method, $silentMode = false, $parent = null) |
||
| 306 | { |
||
| 307 | /** @var Response $response */ |
||
| 308 | $response = $this->request->exec($method, $parent); |
||
| 309 | |||
| 310 | return $this->processResponse($response, $silentMode); |
||
| 311 | } |
||
| 312 | |||
| 313 | /** |
||
| 314 | * Returns a response object and starts the entities processing (if not in silent mode). Method |
||
| 315 | * should be used only when webhook is set. |
||
| 316 | * |
||
| 317 | * @param string $data Raw json data either received from php input or passed manually |
||
| 318 | * @param bool $silentMode If set to true then the events, mapped (in config or by default) |
||
| 319 | * to the entities in the result will not be triggered |
||
| 320 | * |
||
| 321 | * @return Response |
||
| 322 | */ |
||
| 323 | public function getWebhookResponse($data, $silentMode = false) |
||
| 324 | { |
||
| 325 | $response = new Response($data, Update::class); |
||
| 326 | |||
| 327 | return $this->processResponse($response, $silentMode); |
||
| 328 | } |
||
| 329 | |||
| 330 | /** |
||
| 331 | * Processes entities from the response object if not in silent mode or error is received. |
||
| 332 | * |
||
| 333 | * @param Response $response Response object which includes entities |
||
| 334 | * @param bool $silentMode If set to true then the events, mapped (in config or by default) |
||
| 335 | * to the entities in the result will not be triggered |
||
| 336 | * |
||
| 337 | * @return Response |
||
| 338 | */ |
||
| 339 | protected function processResponse(Response $response, $silentMode = false) |
||
| 340 | { |
||
| 341 | if (!empty($response->getEntities()) && ($silentMode === false || $response->isErrorReceived())) { |
||
| 342 | $this->processEntities($response->getEntities()); |
||
| 343 | } |
||
| 344 | |||
| 345 | return $response; |
||
| 346 | } |
||
| 347 | } |
||
| 348 |
When comparing two booleans, it is generally considered safer to use the strict comparison operator.