| Total Complexity | 45 |
| Total Lines | 287 |
| Duplicated Lines | 0 % |
| Changes | 0 | ||
Complex classes like Processor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Processor, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 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; |
||
|
|
|||
| 57 | $this->httpClient = $httpClient; |
||
| 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 |
||
| 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) |
||
| 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) |
||
| 325 | } |
||
| 326 | } |
||
| 327 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.
Either this assignment is in error or an instanceof check should be added for that assignment.