Complex classes like FederatedEventService 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
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 FederatedEventService, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 86 | class FederatedEventService extends NC22Signature { |
||
| 87 | |||
| 88 | |||
| 89 | use TNC22Request; |
||
| 90 | use TStringTools; |
||
| 91 | |||
| 92 | |||
| 93 | /** @var EventWrapperRequest */ |
||
| 94 | private $eventWrapperRequest; |
||
| 95 | |||
| 96 | /** @var RemoteRequest */ |
||
| 97 | private $remoteRequest; |
||
| 98 | |||
| 99 | /** @var ShareLockRequest */ |
||
| 100 | private $shareLockRequest; |
||
| 101 | |||
| 102 | /** @var MemberRequest */ |
||
| 103 | private $memberRequest; |
||
| 104 | |||
| 105 | /** @var RemoteUpstreamService */ |
||
| 106 | private $remoteUpstreamService; |
||
| 107 | |||
| 108 | /** @var InterfaceService */ |
||
| 109 | private $interfaceService; |
||
| 110 | |||
| 111 | /** @var ConfigService */ |
||
| 112 | private $configService; |
||
| 113 | |||
| 114 | |||
| 115 | /** |
||
| 116 | * FederatedEventService constructor. |
||
| 117 | * |
||
| 118 | * @param EventWrapperRequest $eventWrapperRequest |
||
| 119 | * @param RemoteRequest $remoteRequest |
||
| 120 | * @param MemberRequest $memberRequest |
||
| 121 | * @param ShareLockRequest $shareLockRequest |
||
| 122 | * @param RemoteUpstreamService $remoteUpstreamService |
||
| 123 | * @param InterfaceService $interfaceService |
||
| 124 | * @param ConfigService $configService |
||
| 125 | */ |
||
| 126 | public function __construct( |
||
| 127 | EventWrapperRequest $eventWrapperRequest, |
||
| 128 | RemoteRequest $remoteRequest, |
||
| 129 | MemberRequest $memberRequest, |
||
| 130 | ShareLockRequest $shareLockRequest, |
||
| 131 | RemoteUpstreamService $remoteUpstreamService, |
||
| 132 | InterfaceService $interfaceService, |
||
| 133 | ConfigService $configService |
||
| 134 | ) { |
||
| 135 | $this->eventWrapperRequest = $eventWrapperRequest; |
||
| 136 | $this->remoteRequest = $remoteRequest; |
||
| 137 | $this->shareLockRequest = $shareLockRequest; |
||
| 138 | $this->memberRequest = $memberRequest; |
||
| 139 | $this->remoteUpstreamService = $remoteUpstreamService; |
||
| 140 | $this->interfaceService = $interfaceService; |
||
| 141 | $this->configService = $configService; |
||
| 142 | } |
||
| 143 | |||
| 144 | |||
| 145 | /** |
||
| 146 | * Called when creating a new Event. |
||
| 147 | * This method will manage the event locally and upstream the payload if needed. |
||
| 148 | * |
||
| 149 | * @param FederatedEvent $event |
||
| 150 | * |
||
| 151 | * @return array |
||
| 152 | * @throws FederatedEventException |
||
| 153 | * @throws FederatedItemException |
||
| 154 | * @throws InitiatorNotConfirmedException |
||
| 155 | * @throws OwnerNotFoundException |
||
| 156 | * @throws RemoteNotFoundException |
||
| 157 | * @throws RemoteResourceNotFoundException |
||
| 158 | * @throws UnknownRemoteException |
||
| 159 | * @throws RemoteInstanceException |
||
| 160 | * @throws RequestBuilderException |
||
| 161 | */ |
||
| 162 | public function newEvent(FederatedEvent $event): array { |
||
| 163 | $federatedItem = $this->getFederatedItem($event, false); |
||
| 164 | $this->confirmInitiator($event, true); |
||
| 165 | |||
| 166 | if ($event->canBypass(FederatedEvent::BYPASS_CIRCLE)) { |
||
| 167 | $instance = $this->interfaceService->getLocalInstance(); |
||
| 168 | } else { |
||
| 169 | $instance = $event->getCircle()->getInstance(); |
||
| 170 | } |
||
| 171 | |||
| 172 | $event->setSource($instance); |
||
| 173 | if ($this->configService->isLocalInstance($instance)) { |
||
| 174 | $event->setIncomingOrigin($instance); |
||
| 175 | $federatedItem->verify($event); |
||
| 176 | |||
| 177 | if ($event->isDataRequestOnly()) { |
||
| 178 | return $event->getOutcome(); |
||
| 179 | } |
||
| 180 | |||
| 181 | if (!$event->isAsync()) { |
||
| 182 | $federatedItem->manage($event); |
||
| 183 | } |
||
| 184 | |||
| 185 | $this->initBroadcast($event); |
||
| 186 | } else { |
||
| 187 | $this->remoteUpstreamService->confirmEvent($event); |
||
| 188 | if ($event->isDataRequestOnly()) { |
||
| 189 | return $event->getOutcome(); |
||
| 190 | } |
||
| 191 | |||
| 192 | // if (!$event->isAsync()) { |
||
| 193 | // $federatedItem->manage($event); |
||
| 194 | // } |
||
| 195 | } |
||
| 196 | |||
| 197 | return $event->getOutcome(); |
||
| 198 | } |
||
| 199 | |||
| 200 | |||
| 201 | /** |
||
| 202 | * This confirmation is optional, method is just here to avoid going too far away on the process |
||
| 203 | * |
||
| 204 | * @param FederatedEvent $event |
||
| 205 | * @param bool $local |
||
| 206 | * |
||
| 207 | * @throws InitiatorNotConfirmedException |
||
| 208 | */ |
||
| 209 | public function confirmInitiator(FederatedEvent $event, bool $local = false): void { |
||
| 238 | |||
| 239 | |||
| 240 | /** |
||
| 241 | * @param FederatedEvent $event |
||
| 242 | * @param bool $checkLocalOnly |
||
| 243 | * |
||
| 244 | * @return IFederatedItem |
||
| 245 | * @throws FederatedEventException |
||
| 246 | */ |
||
| 247 | public function getFederatedItem(FederatedEvent $event, bool $checkLocalOnly = true): IFederatedItem { |
||
| 248 | $class = $event->getClass(); |
||
| 249 | try { |
||
| 250 | $test = new ReflectionClass($class); |
||
| 251 | } catch (ReflectionException $e) { |
||
| 252 | throw new FederatedEventException('ReflectionException with ' . $class . ': ' . $e->getMessage()); |
||
| 253 | } |
||
| 254 | |||
| 255 | if (!in_array(IFederatedItem::class, $test->getInterfaceNames())) { |
||
| 256 | throw new FederatedEventException($class . ' does not implements IFederatedItem'); |
||
| 257 | } |
||
| 258 | |||
| 259 | $item = OC::$server->get($class); |
||
| 260 | if (!($item instanceof IFederatedItem)) { |
||
| 261 | throw new FederatedEventException($class . ' not an IFederatedItem'); |
||
| 262 | } |
||
| 263 | |||
| 264 | $this->setFederatedEventBypass($event, $item); |
||
| 265 | $this->confirmRequiredCondition($event, $item, $checkLocalOnly); |
||
| 266 | $this->configureEvent($event, $item); |
||
| 267 | |||
| 268 | // $this->confirmSharedItem($event, $item); |
||
| 269 | |||
| 270 | return $item; |
||
| 271 | } |
||
| 272 | |||
| 273 | |||
| 274 | /** |
||
| 275 | * Some event might need to bypass some checks |
||
| 276 | * |
||
| 277 | * @param FederatedEvent $event |
||
| 278 | * @param IFederatedItem $item |
||
| 279 | */ |
||
| 280 | private function setFederatedEventBypass(FederatedEvent $event, IFederatedItem $item) { |
||
| 281 | if ($item instanceof IFederatedItemLoopbackTest) { |
||
| 282 | $event->bypass(FederatedEvent::BYPASS_CIRCLE); |
||
| 283 | $event->bypass(FederatedEvent::BYPASS_INITIATORCHECK); |
||
| 284 | } |
||
| 285 | if ($item instanceof IFederatedItemCircleCheckNotRequired) { |
||
| 286 | $event->bypass(FederatedEvent::BYPASS_LOCALCIRCLECHECK); |
||
| 287 | } |
||
| 288 | if ($item instanceof IFederatedItemMemberCheckNotRequired) { |
||
| 289 | $event->bypass(FederatedEvent::BYPASS_LOCALMEMBERCHECK); |
||
| 290 | } |
||
| 291 | if ($item instanceof IFederatedItemInitiatorCheckNotRequired) { |
||
| 292 | $event->bypass(FederatedEvent::BYPASS_INITIATORCHECK); |
||
| 293 | } |
||
| 294 | if ($item instanceof IFederatedItemInitiatorMembershipNotRequired) { |
||
| 295 | $event->bypass(FederatedEvent::BYPASS_INITIATORMEMBERSHIP); |
||
| 296 | } |
||
| 297 | } |
||
| 298 | |||
| 299 | /** |
||
| 300 | * Some event might require additional check |
||
| 301 | * |
||
| 302 | * @param FederatedEvent $event |
||
| 303 | * @param IFederatedItem $item |
||
| 304 | * @param bool $checkLocalOnly |
||
| 305 | * |
||
| 306 | * @throws FederatedEventException |
||
| 307 | */ |
||
| 308 | private function confirmRequiredCondition( |
||
| 309 | FederatedEvent $event, |
||
| 310 | IFederatedItem $item, |
||
| 311 | bool $checkLocalOnly = true |
||
| 312 | ) { |
||
| 313 | if (!$event->canBypass(FederatedEvent::BYPASS_CIRCLE) && !$event->hasCircle()) { |
||
| 314 | throw new FederatedEventException('FederatedEvent has no Circle linked'); |
||
| 315 | } |
||
| 316 | |||
| 317 | // TODO: enforce IFederatedItemMemberEmpty if no member |
||
| 318 | if ($item instanceof IFederatedItemMemberEmpty) { |
||
| 319 | $event->setMember(null); |
||
| 320 | } else if ($item instanceof IFederatedItemMemberRequired && !$event->hasMember()) { |
||
| 321 | throw new FederatedEventException('FederatedEvent has no Member linked'); |
||
| 322 | } |
||
| 323 | |||
| 324 | if ($event->hasMember() |
||
| 325 | && !($item instanceof IFederatedItemMemberRequired) |
||
| 326 | && !($item instanceof IFederatedItemMemberOptional)) { |
||
| 327 | throw new FederatedEventException( |
||
| 328 | get_class($item) |
||
| 329 | . ' does not implements IFederatedItemMemberOptional nor IFederatedItemMemberRequired' |
||
| 330 | ); |
||
| 331 | } |
||
| 332 | |||
| 333 | if ($item instanceof IFederatedItemMustBeInitializedLocally && $checkLocalOnly) { |
||
| 334 | throw new FederatedEventException('FederatedItem must be executed locally'); |
||
| 335 | } |
||
| 336 | } |
||
| 337 | |||
| 338 | |||
| 339 | /** |
||
| 340 | * @param FederatedEvent $event |
||
| 341 | * @param IFederatedItem $item |
||
| 342 | * |
||
| 343 | * @throws FederatedEventException |
||
| 344 | * @throws FederatedShareBelongingException |
||
| 345 | * @throws FederatedShareNotFoundException |
||
| 346 | * @throws OwnerNotFoundException |
||
| 347 | */ |
||
| 348 | private function confirmSharedItem(FederatedEvent $event, IFederatedItem $item): void { |
||
| 349 | if (!$item instanceof IFederatedItemSharedItem) { |
||
| 350 | return; |
||
| 351 | } |
||
| 352 | |||
| 353 | if ($event->getItemId() === '') { |
||
| 354 | throw new FederatedEventException('FederatedItem must contains ItemId'); |
||
| 355 | } |
||
| 356 | |||
| 357 | if ($this->configService->isLocalInstance($event->getCircle()->getInstance())) { |
||
| 358 | $shareLock = $this->shareLockRequest->getShare($event->getItemId()); |
||
| 359 | if ($shareLock->getInstance() !== $event->getIncomingOrigin()) { |
||
| 360 | throw new FederatedShareBelongingException('ShareLock belongs to another instance'); |
||
| 361 | } |
||
| 362 | } |
||
| 363 | } |
||
| 364 | |||
| 365 | |||
| 366 | /** |
||
| 367 | * @param FederatedEvent $event |
||
| 368 | * @param IFederatedItem $item |
||
| 369 | */ |
||
| 370 | private function configureEvent(FederatedEvent $event, IFederatedItem $item) { |
||
| 381 | |||
| 382 | |||
| 383 | /** |
||
| 384 | * async the process, generate a local request that will be closed. |
||
| 385 | * |
||
| 386 | * @param FederatedEvent $event |
||
| 387 | * |
||
| 388 | * @throws RequestBuilderException |
||
| 389 | */ |
||
| 390 | public function initBroadcast(FederatedEvent $event): void { |
||
| 433 | |||
| 434 | |||
| 435 | /** |
||
| 436 | * @param FederatedEvent $event |
||
| 437 | * |
||
| 438 | * @return RemoteInstance[] |
||
| 439 | * @throws RequestBuilderException |
||
| 440 | */ |
||
| 441 | public function getInstances(FederatedEvent $event): array { |
||
| 484 | |||
| 485 | |||
| 486 | /** |
||
| 487 | * should be used to manage results from events, like sending mails on user creation |
||
| 488 | * |
||
| 489 | * @param string $token |
||
| 490 | */ |
||
| 491 | public function manageResults(string $token): void { |
||
| 522 | |||
| 523 | } |
||
| 524 | |||
| 525 |