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 CreditCardPayment 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 CreditCardPayment, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
29 | class CreditCardPayment implements ICreditCardPayment |
||
30 | { |
||
31 | use TPayload, TAmount, TCustomAttributeContainer, TCardPayment; |
||
32 | |||
33 | const ROOT_NODE = 'CreditCard'; |
||
34 | |||
35 | /** @var DateTime */ |
||
36 | protected $createTimeStamp; |
||
37 | /** @var float */ |
||
38 | protected $amount; |
||
39 | /** @var string */ |
||
40 | protected $responseCode; |
||
41 | /** @var string */ |
||
42 | protected $bankAuthorizationCode; |
||
43 | /** @var string */ |
||
44 | protected $cvv2ResponseCode; |
||
45 | /** @var string */ |
||
46 | protected $avsResponseCode; |
||
47 | /** @var string */ |
||
48 | protected $phoneResponseCode; |
||
49 | /** @var string */ |
||
50 | protected $nameResponseCode; |
||
51 | /** @var string */ |
||
52 | protected $emailResponseCode; |
||
53 | /** @var string */ |
||
54 | protected $extendedAuthDescription; |
||
55 | /** @var string */ |
||
56 | protected $extendedAuthReasonCode; |
||
57 | /** @var float */ |
||
58 | protected $amountAuthorized; |
||
59 | /** @var DateTime */ |
||
60 | protected $expirationDate; |
||
61 | /** @var DateTime */ |
||
62 | protected $startDate; |
||
63 | /** @var string */ |
||
64 | protected $issueNumber; |
||
65 | /** @var string */ |
||
66 | protected $authenticationAvailable; |
||
67 | /** @var string */ |
||
68 | protected $authenticationStatus; |
||
69 | /** @var string */ |
||
70 | protected $cavvUcaf; |
||
71 | /** @var string */ |
||
72 | protected $transactionId; |
||
73 | /** @var string */ |
||
74 | protected $eci; |
||
75 | /** @var string */ |
||
76 | protected $payerAuthenticationResponse; |
||
77 | /** @var string */ |
||
78 | protected $purchasePlanCode; |
||
79 | /** @var string */ |
||
80 | protected $purchasePlanDescription; |
||
81 | /** @var array */ |
||
82 | protected $allowedResponseCodes = [ |
||
83 | self::RESPONSE_CODE_APPROVED, |
||
84 | self::RESPONSE_CODE_ACCEPTORDERWITHEXCEPTION, |
||
85 | self::RESPONSE_CODE_AVS, |
||
86 | self::RESPONSE_CODE_AVSPIN, |
||
87 | self::RESPONSE_CODE_DECLF, |
||
88 | self::RESPONSE_CODE_DECLR, |
||
89 | self::RESPONSE_CODE_DECL, |
||
90 | self::RESPONSE_CODE_PAYMENTPROCESSORTIMEOUT, |
||
91 | self::RESPONSE_CODE_PIN, |
||
92 | self::RESPONSE_CODE_NOK, |
||
93 | self::RESPONSE_CODE_OK, |
||
94 | ]; |
||
95 | |||
96 | /** |
||
97 | * @param IValidatorIterator |
||
98 | * @param ISchemaValidator |
||
99 | * @param IPayloadMap |
||
100 | * @param LoggerInterface |
||
101 | * @param IPayload |
||
102 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
||
103 | */ |
||
104 | public function __construct( |
||
105 | IValidatorIterator $validators, |
||
106 | ISchemaValidator $schemaValidator, |
||
107 | IPayloadMap $payloadMap, |
||
108 | LoggerInterface $logger, |
||
109 | IPayload $parentPayload = null |
||
110 | ) { |
||
111 | $this->logger = $logger; |
||
|
|||
112 | $this->validators = $validators; |
||
113 | $this->schemaValidator = $schemaValidator; |
||
114 | $this->payloadMap = $payloadMap; |
||
115 | $this->parentPayload = $parentPayload; |
||
116 | $this->payloadFactory = new PayloadFactory; |
||
117 | |||
118 | $this->extractionPaths = [ |
||
119 | 'responseCode' => 'string(x:Authorization/x:ResponseCode)', |
||
120 | 'bankAuthorizationCode' => 'string(x:Authorization/x:BankAuthorizationCode)', |
||
121 | 'cvv2ResponseCode' => 'string(x:Authorization/x:CVV2ResponseCode)', |
||
122 | 'avsResponseCode' => 'string(x:Authorization/x:AVSResponseCode)', |
||
123 | 'orderId' => 'string(x:PaymentContext/x:PaymentSessionId)', |
||
124 | 'tenderType' => 'string(x:PaymentContext/x:TenderType)', |
||
125 | 'accountUniqueId' => 'string(x:PaymentContext/x:PaymentAccountUniqueId)', |
||
126 | 'paymentRequestId' => 'string(x:PaymentRequestId)', |
||
127 | ]; |
||
128 | $this->optionalExtractionPaths = [ |
||
129 | 'amount' => 'x:Amount', |
||
130 | 'phoneResponseCode' => 'x:Authorization/x:PhoneResponseCode', |
||
131 | 'nameResponseCode' => 'x:Authorization/x:NameResponseCode', |
||
132 | 'emailResponseCode' => 'x:Authorization/x:EmailResponseCode', |
||
133 | 'extendedAuthDescription' => 'x:Authorization/x:ExtendedAuthorizationResponseCodes/x:ResponseCodeDescription', |
||
134 | 'extendedAuthReasonCode' => 'x:Authorization/x:ExtendedAuthorizationResponseCodes/x:ReasonCode', |
||
135 | 'amountAuthorized' => 'x:Authorization/x:AmountAuthorized', |
||
136 | 'issueNumber' => 'x:IssueNumber', |
||
137 | 'authenticationAvailable' => 'x:SecureVerificationData/x:AuthenticationAvailable', |
||
138 | 'authenticationStatus' => 'x:SecureVerificationData/x:AuthenticationStatus', |
||
139 | 'cavvUcaf' => 'x:SecureVerificationData/x:CavvUcaf', |
||
140 | 'transactionId' => 'x:SecureVerificationData/x:TransactionId', |
||
141 | 'eci' => 'x:SecureVerificationData/x:ECI', |
||
142 | 'payerAuthenticationResponse' => 'x:SecureVerificationData/x:PayerAuthenticationResponse', |
||
143 | 'purchasePlanCode' => 'x:PurchasePlanCode', |
||
144 | 'purchasePlanDescription' => 'x:PurchasePlanDescription', |
||
145 | ]; |
||
146 | $this->booleanExtractionPaths = [ |
||
147 | 'isMockPayment' => 'string(@isMockPayment)', |
||
148 | 'panIsToken' => 'string(x:PaymentContext/x:PaymentAccountUniqueId/@isToken)' |
||
149 | ]; |
||
150 | $this->subpayloadExtractionPaths = [ |
||
151 | 'customAttributes' => 'x:CustomAttributes', |
||
152 | ]; |
||
153 | |||
154 | $this->customAttributes = $this->buildPayloadForInterface(self::CUSTOM_ATTRIBUTE_ITERABLE_INTERFACE); |
||
155 | } |
||
156 | |||
157 | public function getCreateTimeStamp() |
||
161 | |||
162 | public function setCreateTimeStamp(DateTime $createTimeStamp) |
||
163 | { |
||
164 | $this->createTimeStamp = $createTimeStamp; |
||
165 | return $this; |
||
166 | } |
||
167 | |||
168 | public function getAmount() |
||
172 | |||
173 | public function setAmount($amount) |
||
174 | { |
||
175 | $this->amount = $this->sanitizeAmount($amount); |
||
176 | return $this; |
||
177 | } |
||
178 | |||
179 | public function getResponseCode() |
||
183 | |||
184 | public function setResponseCode($responseCode) |
||
185 | { |
||
186 | $this->responseCode = in_array($responseCode, $this->allowedResponseCodes) |
||
187 | ? $responseCode |
||
188 | : null; |
||
189 | return $this; |
||
190 | } |
||
191 | |||
192 | public function getBankAuthorizationCode() |
||
196 | |||
197 | public function setBankAuthorizationCode($bankAuthorizationCode) |
||
198 | { |
||
199 | $this->bankAuthorizationCode = $bankAuthorizationCode; |
||
200 | return $this; |
||
201 | } |
||
202 | |||
203 | public function getCVV2ResponseCode() |
||
207 | |||
208 | public function setCVV2ResponseCode($cvv2ResponseCode) |
||
209 | { |
||
210 | $this->cvv2ResponseCode = $cvv2ResponseCode; |
||
211 | return $this; |
||
212 | } |
||
213 | |||
214 | public function getAVSResponseCode() |
||
218 | |||
219 | public function setAVSResponseCode($avsResponseCode) |
||
220 | { |
||
221 | $this->avsResponseCode = $avsResponseCode; |
||
222 | return $this; |
||
223 | } |
||
224 | |||
225 | public function getPhoneResponseCode() |
||
229 | |||
230 | public function setPhoneResponseCode($phoneResponseCode) |
||
231 | { |
||
232 | $this->phoneResponseCode = $phoneResponseCode; |
||
233 | return $this; |
||
234 | } |
||
235 | |||
236 | public function getNameResponseCode() |
||
240 | |||
241 | public function setNameResponseCode($nameResponseCode) |
||
242 | { |
||
243 | $this->nameResponseCode = $nameResponseCode; |
||
244 | return $this; |
||
245 | } |
||
246 | |||
247 | public function getEmailResponseCode() |
||
251 | |||
252 | public function setEmailResponseCode($emailResponseCode) |
||
253 | { |
||
254 | $this->emailResponseCode = $emailResponseCode; |
||
255 | return $this; |
||
256 | } |
||
257 | |||
258 | public function getExtendedAuthDescription() |
||
262 | |||
263 | public function setExtendedAuthDescription($extendedAuthDescription) |
||
264 | { |
||
265 | $this->extendedAuthDescription = $extendedAuthDescription; |
||
266 | return $this; |
||
267 | } |
||
268 | |||
269 | public function getExtendedAuthReasonCode() |
||
273 | |||
274 | public function setExtendedAuthReasonCode($extendedAuthReasonCode) |
||
275 | { |
||
276 | $this->extendedAuthReasonCode = $extendedAuthReasonCode; |
||
277 | return $this; |
||
278 | } |
||
279 | |||
280 | public function getAmountAuthorized() |
||
284 | |||
285 | public function setAmountAuthorized($amountAuthorized) |
||
286 | { |
||
287 | $this->amountAuthorized = $this->sanitizeAmount($amountAuthorized); |
||
288 | return $this; |
||
289 | } |
||
290 | |||
291 | public function getExpirationDate() |
||
295 | |||
296 | public function setExpirationDate(DateTime $expirationDate) |
||
297 | { |
||
298 | $this->expirationDate = $expirationDate; |
||
299 | return $this; |
||
300 | } |
||
301 | |||
302 | public function getStartDate() |
||
306 | |||
307 | public function setStartDate(DateTime $startDate) |
||
308 | { |
||
309 | $this->startDate = $startDate; |
||
310 | return $this; |
||
311 | } |
||
312 | |||
313 | public function getIssueNumber() |
||
317 | |||
318 | public function setIssueNumber($issueNumber) |
||
319 | { |
||
320 | $this->issueNumber = $issueNumber; |
||
321 | return $this; |
||
322 | } |
||
323 | |||
324 | public function getAuthenticationAvailable() |
||
328 | |||
329 | public function setAuthenticationAvailable($authenticationAvailable) |
||
330 | { |
||
331 | $this->authenticationAvailable = $authenticationAvailable; |
||
332 | return $this; |
||
333 | } |
||
334 | |||
335 | public function getAuthenticationStatus() |
||
339 | |||
340 | public function setAuthenticationStatus($authenticationStatus) |
||
341 | { |
||
342 | $this->authenticationStatus = $authenticationStatus; |
||
343 | return $this; |
||
344 | } |
||
345 | |||
346 | public function getCavvUcaf() |
||
350 | |||
351 | public function setCavvUcaf($cavvUcaf) |
||
352 | { |
||
353 | $this->cavvUcaf = $this->cleanString($cavvUcaf, 64); |
||
354 | return $this; |
||
355 | } |
||
356 | |||
357 | public function getTransactionId() |
||
361 | |||
362 | public function setTransactionId($transactionId) |
||
363 | { |
||
364 | $this->transactionId = $this->cleanString($transactionId, 64); |
||
365 | return $this; |
||
366 | } |
||
367 | |||
368 | public function getECI() |
||
372 | |||
373 | public function setECI($eci) |
||
374 | { |
||
375 | $this->eci = $eci; |
||
376 | return $this; |
||
377 | } |
||
378 | |||
379 | public function getPayerAuthenticationResponse() |
||
383 | |||
384 | public function setPayerAuthenticationResponse($payerAuthenticationResponse) |
||
385 | { |
||
386 | $this->payerAuthenticationResponse = $this->cleanString($payerAuthenticationResponse, 10000); |
||
387 | return $this; |
||
388 | } |
||
389 | |||
390 | public function getPurchasePlanCode() |
||
394 | |||
395 | public function setPurchasePlanCode($purchasePlanCode) |
||
396 | { |
||
397 | $this->purchasePlanCode = $purchasePlanCode; |
||
398 | return $this; |
||
399 | } |
||
400 | |||
401 | public function getPurchasePlanDescription() |
||
405 | |||
406 | public function setPurchasePlanDescription($purchasePlanDescription) |
||
407 | { |
||
408 | $this->purchasePlanDescription = $purchasePlanDescription; |
||
409 | return $this; |
||
410 | } |
||
411 | |||
412 | protected function serializeContents() |
||
413 | { |
||
414 | return $this->serializePaymentContext() |
||
415 | . $this->serializePaymentRequestId() |
||
416 | . "<CreateTimeStamp>{$this->getCreateTimeStamp()->format('c')}</CreateTimeStamp>" |
||
417 | . $this->serializeOptionalAmount('Amount', $this->getAmount()) |
||
418 | . $this->serializeAuthorizations() |
||
419 | . $this->serializeOptionalDateValue('ExpirationDate', 'Y-m', $this->getExpirationDate()) |
||
420 | . $this->serializeOptionalDateValue('StartDate', 'Y-m', $this->getStartDate()) |
||
421 | . $this->serializeOptionalValue('IssueNumber', $this->xmlEncode($this->getIssueNumber())) |
||
422 | . $this->serializeSecureVerificationData() |
||
423 | . $this->serializeOptionalValue('PurchasePlanCode', $this->xmlEncode($this->getPurchasePlanCode())) |
||
424 | . $this->serializeOptionalValue('PurchasePlanDescription', $this->xmlEncode($this->getPurchasePlanDescription())) |
||
425 | . $this->getCustomAttributes()->serialize(); |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * Serialize the credit card authorizations. |
||
430 | * |
||
431 | * @return string |
||
432 | */ |
||
433 | protected function serializeAuthorizations() |
||
434 | { |
||
435 | return '<Authorization>' |
||
436 | . "<ResponseCode>{$this->xmlEncode($this->getResponseCode())}</ResponseCode>" |
||
437 | . "<BankAuthorizationCode>{$this->xmlEncode($this->getBankAuthorizationCode())}</BankAuthorizationCode>" |
||
438 | . "<CVV2ResponseCode>{$this->xmlEncode($this->getCVV2ResponseCode())}</CVV2ResponseCode>" |
||
439 | . "<AVSResponseCode>{$this->xmlEncode($this->getAVSResponseCode())}</AVSResponseCode>" |
||
440 | . $this->serializeOptionalValue('PhoneResponseCode', $this->xmlEncode($this->getPhoneResponseCode())) |
||
441 | . $this->serializeOptionalValue('NameResponseCode', $this->xmlEncode($this->getNameResponseCode())) |
||
442 | . $this->serializeOptionalValue('EmailResponseCode', $this->xmlEncode($this->getEmailResponseCode())) |
||
443 | . $this->serializeExtendedAuthorizations() |
||
444 | . $this->serializeOptionalAmount('AmountAuthorized', $this->getAmountAuthorized()) |
||
445 | . '</Authorization>'; |
||
446 | } |
||
447 | |||
448 | /** |
||
449 | * Serialize the extended authorizations. Only include if both the extended |
||
450 | * auth reason code and extended auth response code description are included. |
||
451 | * Return an empty string, no serialization, if either is missing. |
||
452 | * |
||
453 | * @return string |
||
454 | */ |
||
455 | protected function serializeExtendedAuthorizations() |
||
456 | { |
||
457 | $code = $this->getExtendedAuthReasonCode(); |
||
458 | $description = $this->getExtendedAuthDescription(); |
||
459 | return $code && $description |
||
460 | ? '<ExtendedAuthorizationResponseCodes>' |
||
461 | . "<ResponseCodeDescription>{$this->xmlEncode($description)}</ResponseCodeDescription>" |
||
462 | . "<ReasonCode>{$this->xmlEncode($code)}</ReasonCode>" |
||
463 | . '</ExtendedAuthorizationResponseCodes>' |
||
464 | : ''; |
||
465 | } |
||
466 | |||
467 | /** |
||
468 | * Serialize sercure verification data (Verified by VISA). Will only be |
||
469 | * included if a payer authentication response is included. |
||
470 | * |
||
471 | * @return string |
||
472 | */ |
||
473 | protected function serializeSecureVerificationData() |
||
474 | { |
||
475 | $payerAuthResponse = $this->getPayerAuthenticationResponse(); |
||
476 | if ($payerAuthResponse) { |
||
477 | return '<SecureVerificationData>' |
||
478 | . $this->serializeOptionalValue('AuthenticationAvailable', $this->xmlEncode($this->getAuthenticationAvailable())) |
||
479 | . $this->serializeOptionalValue('AuthenticationStatus', $this->xmlEncode($this->getAuthenticationStatus())) |
||
480 | . $this->serializeOptionalValue('CavvUcaf', $this->xmlEncode($this->getCavvUcaf())) |
||
481 | . $this->serializeOptionalValue('TransactionId', $this->xmlEncode($this->getTransactionId())) |
||
482 | . $this->serializeOptionalValue('ECI', $this->xmlEncode($this->getECI())) |
||
483 | . "<PayerAuthenticationResponse>{$this->xmlEncode($this->getPayerAuthenticationResponse())}</PayerAuthenticationResponse>" |
||
484 | . '</SecureVerificationData>'; |
||
485 | } |
||
486 | return ''; |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Deserialize date time fields as DateTime objects instead of plain string |
||
491 | * values. |
||
492 | * |
||
493 | * @return self |
||
494 | */ |
||
495 | protected function deserializeExtra($serializedPayload) |
||
496 | { |
||
497 | $xpath = $this->getPayloadAsXPath($serializedPayload); |
||
498 | $dateTimePaths = [ |
||
499 | 'createTimeStamp' => 'string(x:CreateTimeStamp)', |
||
500 | 'expirationDate' => 'string(x:ExpirationDate)', |
||
501 | 'startDate' => 'string(x:StartDate)', |
||
502 | ]; |
||
503 | foreach ($dateTimePaths as $prop => $extractionPath) { |
||
504 | $value = $xpath->evaluate($extractionPath); |
||
505 | $this->$prop = $value ? new DateTime($value) : null; |
||
506 | } |
||
507 | return $this; |
||
508 | } |
||
509 | |||
510 | View Code Duplication | protected function getRootAttributes() |
|
511 | { |
||
512 | $isMockPayment = $this->getIsMockPayment(); |
||
513 | return !is_null($isMockPayment) |
||
514 | ? ['isMockPayment' => $this->convertBooleanToString($isMockPayment)] |
||
515 | : []; |
||
516 | } |
||
517 | |||
518 | protected function getRootNodeName() |
||
522 | |||
523 | protected function getXmlNamespace() |
||
527 | } |
||
528 |
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..