Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like SingleMemberAdd 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 SingleMemberAdd, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
98 | class SingleMemberAdd implements |
||
99 | IFederatedItem, |
||
100 | IFederatedItemAsyncProcess, |
||
101 | IFederatedItemHighSeverity, |
||
102 | IFederatedItemMemberRequired, |
||
103 | IFederatedItemMemberCheckNotRequired { |
||
104 | |||
105 | |||
106 | use TNC22Deserialize; |
||
107 | |||
108 | |||
109 | use TStringTools; |
||
110 | use TNC22Logger; |
||
111 | |||
112 | |||
113 | /** @var IUserManager */ |
||
114 | protected $userManager; |
||
115 | |||
116 | /** @var MemberRequest */ |
||
117 | protected $memberRequest; |
||
118 | |||
119 | /** @var FederatedUserService */ |
||
120 | protected $federatedUserService; |
||
121 | |||
122 | /** @var RemoteStreamService */ |
||
123 | protected $remoteStreamService; |
||
124 | |||
125 | /** @var CircleService */ |
||
126 | protected $circleService; |
||
127 | |||
128 | /** @var MemberService */ |
||
129 | protected $memberService; |
||
130 | |||
131 | /** @var MembershipService */ |
||
132 | protected $membershipService; |
||
133 | |||
134 | /** @var EventService */ |
||
135 | protected $eventService; |
||
136 | |||
137 | /** @var ConfigService */ |
||
138 | protected $configService; |
||
139 | |||
140 | |||
141 | /** |
||
142 | * SingleMemberAdd constructor. |
||
143 | * |
||
144 | * @param IUserManager $userManager |
||
145 | * @param MemberRequest $memberRequest |
||
146 | * @param FederatedUserService $federatedUserService |
||
147 | * @param RemoteStreamService $remoteStreamService |
||
148 | * @param CircleService $circleService |
||
149 | * @param MemberService $memberService |
||
150 | * @param MembershipService $membershipService |
||
151 | * @param EventService $eventService |
||
152 | * @param ConfigService $configService |
||
153 | */ |
||
154 | View Code Duplication | public function __construct( |
|
|
|||
155 | IUserManager $userManager, |
||
156 | MemberRequest $memberRequest, |
||
157 | FederatedUserService $federatedUserService, |
||
158 | RemoteStreamService $remoteStreamService, |
||
159 | CircleService $circleService, |
||
160 | MemberService $memberService, |
||
161 | MembershipService $membershipService, |
||
162 | EventService $eventService, |
||
163 | ConfigService $configService |
||
164 | ) { |
||
165 | $this->userManager = $userManager; |
||
166 | $this->memberRequest = $memberRequest; |
||
167 | $this->federatedUserService = $federatedUserService; |
||
168 | $this->remoteStreamService = $remoteStreamService; |
||
169 | $this->circleService = $circleService; |
||
170 | $this->memberService = $memberService; |
||
171 | $this->membershipService = $membershipService; |
||
172 | $this->eventService = $eventService; |
||
173 | $this->configService = $configService; |
||
174 | } |
||
175 | |||
176 | |||
177 | /** |
||
178 | * @param FederatedEvent $event |
||
179 | * |
||
180 | * @throws FederatedItemBadRequestException |
||
181 | * @throws FederatedItemNotFoundException |
||
182 | * @throws FederatedItemServerException |
||
183 | * @throws FederatedItemRemoteException |
||
184 | * @throws FederatedItemException |
||
185 | * @throws RequestBuilderException |
||
186 | */ |
||
187 | public function verify(FederatedEvent $event): void { |
||
188 | $member = $event->getMember(); |
||
189 | $circle = $event->getCircle(); |
||
190 | $initiator = $circle->getInitiator(); |
||
191 | |||
192 | $initiatorHelper = new MemberHelper($initiator); |
||
193 | $initiatorHelper->mustBeModerator(); |
||
194 | |||
195 | $member = $this->generateMember($event, $circle, $member); |
||
196 | |||
197 | $event->setMembers([$member]); |
||
198 | $event->setOutcome($this->serialize($member)); |
||
199 | |||
200 | return; |
||
201 | |||
202 | |||
203 | // $member = $this->membersRequest->getFreshNewMember( |
||
204 | // $circle->getUniqueId(), $ident, $eventMember->getType(), $eventMember->getInstance() |
||
205 | // ); |
||
206 | // $member->hasToBeInviteAble() |
||
207 | // |
||
208 | // $this->membersService->addMemberBasedOnItsType($circle, $member); |
||
209 | // |
||
210 | // $password = ''; |
||
211 | // $sendPasswordByMail = false; |
||
212 | // if ($this->configService->enforcePasswordProtection($circle)) { |
||
213 | // if ($circle->getSetting('password_single_enabled') === 'true') { |
||
214 | // $password = $circle->getPasswordSingle(); |
||
215 | // } else { |
||
216 | // $sendPasswordByMail = true; |
||
217 | // $password = $this->miscService->token(15); |
||
218 | // } |
||
219 | // } |
||
220 | // |
||
221 | // $event->setData( |
||
222 | // new SimpleDataStore( |
||
223 | // [ |
||
224 | // 'password' => $password, |
||
225 | // 'passwordByMail' => $sendPasswordByMail |
||
226 | // ] |
||
227 | // ) |
||
228 | // ); |
||
229 | } |
||
230 | |||
231 | |||
232 | /** |
||
233 | * @param FederatedEvent $event |
||
234 | * |
||
235 | * @throws InvalidIdException |
||
236 | * @throws RemoteNotFoundException |
||
237 | * @throws RequestBuilderException |
||
238 | * @throws UnknownRemoteException |
||
239 | */ |
||
240 | View Code Duplication | public function manage(FederatedEvent $event): void { |
|
241 | $member = $event->getMember(); |
||
242 | if (!$this->memberService->insertOrUpdate($member)) { |
||
243 | return; |
||
244 | } |
||
245 | |||
246 | if ($member->getStatus() === Member::STATUS_INVITED) { |
||
247 | $this->eventService->memberInviting($event); |
||
248 | } else { |
||
249 | $this->eventService->memberAdding($event); |
||
250 | } |
||
251 | |||
252 | // |
||
253 | // // |
||
254 | // // TODO: verifiez comment se passe le cached name sur un member_add |
||
255 | // // |
||
256 | // $cachedName = $member->getCachedName(); |
||
257 | // $password = $event->getData() |
||
258 | // ->g('password'); |
||
259 | // |
||
260 | // $shares = $this->generateUnknownSharesLinks($circle, $member, $password); |
||
261 | // $result = [ |
||
262 | // 'unknownShares' => $shares, |
||
263 | // 'cachedName' => $cachedName |
||
264 | // ]; |
||
265 | // |
||
266 | // if ($member->getType() === DeprecatedMember::TYPE_CONTACT |
||
267 | // && $this->configService->isLocalInstance($member->getInstance())) { |
||
268 | // $result['contact'] = $this->miscService->getInfosFromContact($member); |
||
269 | // } |
||
270 | // |
||
271 | // $event->setResult(new SimpleDataStore($result)); |
||
272 | // $this->eventsService->onMemberNew($circle, $member); |
||
273 | } |
||
274 | |||
275 | |||
276 | /** |
||
277 | * @param FederatedEvent $event |
||
278 | * @param array $results |
||
279 | */ |
||
280 | View Code Duplication | public function result(FederatedEvent $event, array $results): void { |
|
281 | $member = $event->getMember(); |
||
282 | if ($member->getStatus() === Member::STATUS_INVITED) { |
||
283 | $this->eventService->memberInvited($event, $results); |
||
284 | } else { |
||
285 | $this->eventService->memberAdded($event, $results); |
||
286 | } |
||
287 | |||
288 | // $password = $cachedName = ''; |
||
289 | // $circle = $member = null; |
||
290 | // $links = []; |
||
291 | // $recipients = []; |
||
292 | // foreach ($events as $event) { |
||
293 | // $data = $event->getData(); |
||
294 | // if ($data->gBool('passwordByMail') !== false) { |
||
295 | // $password = $data->g('password'); |
||
296 | // } |
||
297 | // $circle = $event->getDeprecatedCircle(); |
||
298 | // $member = $event->getMember(); |
||
299 | // $result = $event->getResult(); |
||
300 | // if ($result->g('cachedName') !== '') { |
||
301 | // $cachedName = $result->g('cachedName'); |
||
302 | // } |
||
303 | // |
||
304 | // $links = array_merge($links, $result->gArray('unknownShares')); |
||
305 | // $contact = $result->gArray('contact'); |
||
306 | // if (!empty($contact)) { |
||
307 | // $recipients = $contact['emails']; |
||
308 | // } |
||
309 | // } |
||
310 | // |
||
311 | // if (empty($links) || $circle === null || $member === null) { |
||
312 | // return; |
||
313 | // } |
||
314 | // |
||
315 | // if ($cachedName !== '') { |
||
316 | // $member->setCachedName($cachedName); |
||
317 | // $this->membersService->updateMember($member); |
||
318 | // } |
||
319 | // |
||
320 | // if ($member->getType() === DeprecatedMember::TYPE_MAIL |
||
321 | // || $member->getType() === DeprecatedMember::TYPE_CONTACT) { |
||
322 | // if ($member->getType() === DeprecatedMember::TYPE_MAIL) { |
||
323 | // $recipients = [$member->getUserId()]; |
||
324 | // } |
||
325 | // |
||
326 | // foreach ($recipients as $recipient) { |
||
327 | // $this->memberIsMailbox($circle, $recipient, $links, $password); |
||
328 | // } |
||
329 | // } |
||
330 | } |
||
331 | |||
332 | |||
333 | /** |
||
334 | * @param FederatedEvent $event |
||
335 | * @param Circle $circle |
||
336 | * @param Member $member |
||
337 | * |
||
338 | * @return Member |
||
339 | * @throws CircleNotFoundException |
||
340 | * @throws FederatedItemBadRequestException |
||
341 | * @throws FederatedItemException |
||
342 | * @throws FederatedUserException |
||
343 | * @throws FederatedUserNotFoundException |
||
344 | * @throws InvalidIdException |
||
345 | * @throws MembersLimitException |
||
346 | * @throws OwnerNotFoundException |
||
347 | * @throws RemoteInstanceException |
||
348 | * @throws RemoteNotFoundException |
||
349 | * @throws RemoteResourceNotFoundException |
||
350 | * @throws SingleCircleNotFoundException |
||
351 | * @throws UnknownRemoteException |
||
352 | * @throws UserTypeNotFoundException |
||
353 | * @throws RequestBuilderException |
||
354 | */ |
||
355 | protected function generateMember(FederatedEvent $event, Circle $circle, Member $member): Member { |
||
356 | try { |
||
357 | if ($member->getSingleId() !== '') { |
||
358 | $userId = $member->getSingleId() . '@' . $member->getInstance(); |
||
359 | $federatedUser = $this->federatedUserService->getFederatedUser($userId, Member::TYPE_SINGLE); |
||
360 | |||
361 | } else { |
||
362 | $userId = $member->getUserId() . '@' . $member->getInstance(); |
||
363 | $federatedUser = $this->federatedUserService->getFederatedUser( |
||
364 | $userId, |
||
365 | $member->getUserType() |
||
366 | ); |
||
367 | } |
||
368 | |||
369 | } catch (MemberNotFoundException $e) { |
||
370 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[120], 120); |
||
371 | } |
||
372 | |||
373 | if ($federatedUser->getBasedOn()->isConfig(Circle::CFG_ROOT)) { |
||
374 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[125], 125); |
||
375 | } |
||
376 | |||
377 | if ($member->getSingleId() === $circle->getSingleId()) { |
||
378 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[128], 128); |
||
379 | } |
||
380 | |||
381 | if (!$this->configService->isLocalInstance($member->getInstance())) { |
||
382 | if ($circle->isConfig(Circle::CFG_LOCAL)) { |
||
383 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[126], 126); |
||
384 | } |
||
385 | |||
386 | if (!$circle->isConfig(Circle::CFG_FEDERATED)) { |
||
387 | $remoteInstance = $this->remoteStreamService->getCachedRemoteInstance($member->getInstance()); |
||
388 | if ($remoteInstance->getType() !== RemoteInstance::TYPE_GLOBALSCALE) { |
||
389 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[127], 127); |
||
390 | } |
||
391 | } |
||
392 | } |
||
393 | |||
394 | $member->importFromIFederatedUser($federatedUser); |
||
395 | $member->setCircleId($circle->getSingleId()); |
||
396 | $member->setCircle($circle); |
||
397 | |||
398 | $this->confirmPatron($event, $member); |
||
399 | $this->manageMemberStatus($circle, $member); |
||
400 | |||
401 | $this->circleService->confirmCircleNotFull($circle); |
||
402 | |||
403 | // The idea is that adding the member during the self::verify() will help during the broadcasting |
||
404 | // of the event to Federated RemoteInstance for their first member. |
||
405 | $this->memberRequest->insertOrUpdate($member); |
||
406 | |||
407 | return $member; |
||
408 | } |
||
409 | |||
410 | |||
411 | /** |
||
412 | * @param Circle $circle |
||
413 | * @param Member $member |
||
414 | * |
||
415 | * @throws FederatedItemBadRequestException |
||
416 | * @throws RequestBuilderException |
||
417 | */ |
||
418 | private function manageMemberStatus(Circle $circle, Member $member) { |
||
419 | try { |
||
420 | $knownMember = $this->memberRequest->searchMember($member); |
||
421 | $member->setId($knownMember->getId()); |
||
422 | |||
423 | if ($knownMember->getLevel() === Member::LEVEL_NONE) { |
||
424 | switch ($knownMember->getStatus()) { |
||
425 | case Member::STATUS_BLOCKED: |
||
426 | if ($circle->isConfig(Circle::CFG_INVITE)) { |
||
427 | $member->setStatus(Member::STATUS_INVITED); |
||
428 | } |
||
429 | |||
430 | return; |
||
431 | |||
432 | case Member::STATUS_REQUEST: |
||
433 | $member->setLevel(Member::LEVEL_MEMBER); |
||
434 | $member->setStatus(Member::STATUS_MEMBER); |
||
435 | |||
436 | return; |
||
437 | |||
438 | case Member::STATUS_INVITED: |
||
439 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[123], 123); |
||
440 | } |
||
441 | } |
||
442 | |||
443 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[122], 122); |
||
444 | } catch (MemberNotFoundException $e) { |
||
445 | $member->setId($this->token(ManagedModel::ID_LENGTH)); |
||
446 | |||
447 | View Code Duplication | if ($circle->isConfig(Circle::CFG_INVITE)) { |
|
448 | $member->setStatus(Member::STATUS_INVITED); |
||
449 | } else { |
||
450 | $member->setLevel(Member::LEVEL_MEMBER); |
||
451 | $member->setStatus(Member::STATUS_MEMBER); |
||
452 | } |
||
453 | } |
||
454 | } |
||
455 | |||
456 | |||
457 | /** |
||
458 | * @param FederatedEvent $event |
||
459 | * @param Member $member |
||
460 | * |
||
461 | * @throws FederatedItemBadRequestException |
||
462 | * @throws FederatedUserException |
||
463 | * @throws RemoteNotFoundException |
||
464 | * @throws RequestBuilderException |
||
465 | * @throws UnknownRemoteException |
||
466 | */ |
||
467 | private function confirmPatron(FederatedEvent $event, Member $member): void { |
||
468 | if (!$member->hasInvitedBy()) { |
||
469 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[129], 129); |
||
470 | } |
||
471 | |||
472 | $patron = $member->getInvitedBy(); |
||
473 | if ($patron->getInstance() !== $event->getSender()) { |
||
474 | throw new FederatedItemBadRequestException(StatusCode::$MEMBER_ADD[130], 130); |
||
475 | } |
||
476 | |||
477 | $this->federatedUserService->confirmSingleIdUniqueness($patron); |
||
478 | } |
||
479 | |||
480 | |||
481 | /** |
||
482 | * confirm the validity of a UserId, based on UserType. |
||
483 | * |
||
484 | * @param IFederatedUser $member |
||
485 | * |
||
486 | * @throws FederatedUserException |
||
487 | * @throws InvalidIdException |
||
488 | * @throws UserTypeNotFoundException |
||
489 | * @throws CircleNotFoundException |
||
490 | * @throws FederatedUserNotFoundException |
||
491 | * @throws OwnerNotFoundException |
||
492 | * @throws RemoteInstanceException |
||
493 | * @throws RemoteNotFoundException |
||
494 | * @throws RemoteResourceNotFoundException |
||
495 | * @throws UnknownRemoteException |
||
496 | * @throws InvalidItemException |
||
497 | * @throws RequestNetworkException |
||
498 | * @throws SignatoryException |
||
499 | */ |
||
500 | private function confirmMember(IFederatedUser $member): void { |
||
514 | |||
515 | |||
516 | /** |
||
517 | * @param IFederatedUser $member |
||
518 | * |
||
519 | * @throws NoUserException |
||
520 | */ |
||
521 | private function confirmMemberTypeUser(IFederatedUser $member): void { |
||
535 | |||
536 | // /** |
||
537 | // * Verify if a local account is valid. |
||
538 | // * |
||
539 | // * @param $ident |
||
540 | // * @param $type |
||
541 | // * |
||
542 | // * @param string $instance |
||
543 | // * |
||
544 | // * @throws NoUserException |
||
545 | // */ |
||
546 | // private function verifyIdentLocalMember(&$ident, $type, string $instance = '') { |
||
547 | // if ($type !== DeprecatedMember::TYPE_USER) { |
||
548 | // return; |
||
549 | // } |
||
550 | // |
||
551 | // if ($instance === '') { |
||
552 | // try { |
||
553 | // $ident = $this->miscService->getRealUserId($ident); |
||
554 | // } catch (NoUserException $e) { |
||
555 | // throw new NoUserException($this->l10n->t("This user does not exist")); |
||
556 | // } |
||
557 | // } |
||
558 | // } |
||
559 | // |
||
560 | // |
||
561 | // /** |
||
562 | // * Verify if a mail have a valid format. |
||
563 | // * |
||
564 | // * @param string $ident |
||
565 | // * @param int $type |
||
566 | // * |
||
567 | // * @throws EmailAccountInvalidFormatException |
||
568 | // */ |
||
569 | // private function verifyIdentEmailAddress(string $ident, int $type) { |
||
570 | // if ($type !== DeprecatedMember::TYPE_MAIL) { |
||
571 | // return; |
||
572 | // } |
||
573 | // |
||
574 | // if ($this->configService->isAccountOnly()) { |
||
575 | // throw new EmailAccountInvalidFormatException( |
||
576 | // $this->l10n->t('You cannot add a email address as member of your Circle') |
||
577 | // ); |
||
578 | // } |
||
579 | // |
||
580 | // if (!filter_var($ident, FILTER_VALIDATE_EMAIL)) { |
||
581 | // throw new EmailAccountInvalidFormatException( |
||
582 | // $this->l10n->t('Email format is not valid') |
||
583 | // ); |
||
584 | // } |
||
585 | // } |
||
586 | // |
||
587 | // |
||
588 | // /** |
||
589 | // * Verify if a contact exist in current user address books. |
||
590 | // * |
||
591 | // * @param $ident |
||
592 | // * @param $type |
||
593 | // * |
||
594 | // * @throws NoUserException |
||
595 | // * @throws EmailAccountInvalidFormatException |
||
596 | // */ |
||
597 | // private function verifyIdentContact(&$ident, $type) { |
||
598 | // if ($type !== DeprecatedMember::TYPE_CONTACT) { |
||
599 | // return; |
||
600 | // } |
||
601 | // |
||
602 | // if ($this->configService->isAccountOnly()) { |
||
603 | // throw new EmailAccountInvalidFormatException( |
||
604 | // $this->l10n->t('You cannot add a contact as member of your Circle') |
||
605 | // ); |
||
606 | // } |
||
607 | // |
||
608 | // $tmpContact = $this->userId . ':' . $ident; |
||
609 | // $result = MiscService::getContactData($tmpContact); |
||
610 | // if (empty($result)) { |
||
611 | // throw new NoUserException($this->l10n->t("This contact is not available")); |
||
612 | // } |
||
613 | // |
||
614 | // $ident = $tmpContact; |
||
615 | // } |
||
616 | |||
617 | |||
618 | /** |
||
619 | * @param DeprecatedCircle $circle |
||
620 | * @param string $recipient |
||
621 | * @param array $links |
||
622 | * @param string $password |
||
623 | */ |
||
624 | View Code Duplication | private function memberIsMailbox( |
|
644 | |||
645 | |||
646 | /** |
||
647 | * @param DeprecatedCircle $circle |
||
648 | * @param DeprecatedMember $member |
||
649 | * @param string $password |
||
650 | * |
||
651 | * @return array |
||
652 | */ |
||
653 | View Code Duplication | private function generateUnknownSharesLinks( |
|
668 | |||
669 | |||
670 | /** |
||
671 | * @param DeprecatedMember $member |
||
672 | * |
||
673 | * @return array |
||
674 | */ |
||
675 | View Code Duplication | private function getUnknownShares(DeprecatedMember $member): array { |
|
693 | |||
694 | |||
695 | /** |
||
696 | * @param array $share |
||
697 | * @param DeprecatedMember $member |
||
698 | * @param string $password |
||
699 | * |
||
700 | * @return array |
||
701 | * @throws TokenDoesNotExistException |
||
702 | */ |
||
703 | View Code Duplication | private function getMailLinkFromShare(array $share, DeprecatedMember $member, string $password = '') { |
|
718 | |||
719 | |||
720 | /** |
||
721 | * @param string $author |
||
722 | * @param string $circleName |
||
723 | * |
||
724 | * @return IEMailTemplate |
||
725 | */ |
||
726 | View Code Duplication | private function generateMailExitingShares(string $author, string $circleName): IEMailTemplate { |
|
735 | |||
736 | /** |
||
737 | * @param IEMailTemplate $emailTemplate |
||
738 | * @param array $links |
||
739 | */ |
||
740 | View Code Duplication | private function fillMailExistingShares(IEMailTemplate $emailTemplate, array $links) { |
|
747 | |||
748 | |||
749 | /** |
||
750 | * @param IEMailTemplate $emailTemplate |
||
751 | * @param string $author |
||
752 | * @param string $recipient |
||
753 | * |
||
754 | * @throws Exception |
||
755 | */ |
||
756 | View Code Duplication | private function sendMailExistingShares(IEMailTemplate $emailTemplate, string $author, string $recipient |
|
773 | |||
774 | |||
775 | /** |
||
776 | * @param string $author |
||
777 | * @param string $email |
||
778 | * @param string $password |
||
779 | * |
||
780 | * @throws Exception |
||
781 | */ |
||
782 | protected function sendPasswordExistingShares(string $author, string $email, string $password) { |
||
844 | |||
845 | } |
||
846 | |||
847 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.