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 CreditCardAuthRequest 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 CreditCardAuthRequest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
26 | class CreditCardAuthRequest implements ICreditCardAuthRequest |
||
27 | { |
||
28 | use TTopLevelPayload, TPaymentContext, TBillingAddress, TShippingAddress; |
||
29 | |||
30 | /** @var string */ |
||
31 | protected $requestId; |
||
32 | /** @var \DateTime */ |
||
33 | protected $expirationDate; |
||
34 | /** @var string */ |
||
35 | protected $cardSecurityCode; |
||
36 | /** @var float */ |
||
37 | protected $amount; |
||
38 | /** @var string */ |
||
39 | protected $currencyCode; |
||
40 | /** @var string */ |
||
41 | protected $billingFirstName; |
||
42 | /** @var string */ |
||
43 | protected $billingLastName; |
||
44 | /** @var string */ |
||
45 | protected $billingPhone; |
||
46 | /** @var string */ |
||
47 | protected $customerEmail; |
||
48 | /** @var string */ |
||
49 | protected $customerIpAddress; |
||
50 | /** @var string */ |
||
51 | protected $shipToFirstName; |
||
52 | /** @var string */ |
||
53 | protected $shipToLastName; |
||
54 | /** @var string */ |
||
55 | protected $shipToPhone; |
||
56 | /** @var bool */ |
||
57 | protected $isRequestToCorrectCVVOrAVSError; |
||
58 | /** @var string */ |
||
59 | protected $authenticationAvailable; |
||
60 | /** @var string */ |
||
61 | protected $authenticationStatus; |
||
62 | /** @var string */ |
||
63 | protected $cavvUcaf; |
||
64 | /** @var string */ |
||
65 | protected $transactionId; |
||
66 | /** @var string */ |
||
67 | protected $eci; |
||
68 | /** @var string */ |
||
69 | protected $payerAuthenticationResponse; |
||
70 | |||
71 | /** |
||
72 | * @param IValidatorIterator |
||
73 | * @param ISchemaValidator |
||
74 | * @param IPayloadMap |
||
75 | * @param LoggerInterface |
||
76 | * @param IPayload |
||
77 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
||
78 | */ |
||
79 | public function __construct( |
||
80 | IValidatorIterator $validators, |
||
81 | ISchemaValidator $schemaValidator, |
||
82 | IPayloadMap $payloadMap, |
||
|
|||
83 | LoggerInterface $logger, |
||
84 | IPayload $parentPayload = null |
||
85 | ) { |
||
86 | $this->logger = $logger; |
||
87 | $this->validators = $validators; |
||
88 | $this->schemaValidator = $schemaValidator; |
||
89 | $this->parentPayload = $parentPayload; |
||
90 | |||
91 | $this->extractionPaths = [ |
||
92 | 'requestId' => 'string(@requestId)', |
||
93 | 'orderId' => 'string(x:PaymentContext/x:OrderId)', |
||
94 | 'cardNumber' => |
||
95 | 'string(x:PaymentContext/x:EncryptedPaymentAccountUniqueId|x:PaymentContext/x:PaymentAccountUniqueId)', |
||
96 | 'expirationDate' => 'string(x:ExpirationDate)', |
||
97 | 'cardSecurityCode' => 'string(x:CardSecurityCode|x:EncryptedCardSecurityCode)', |
||
98 | 'amount' => 'number(x:Amount)', |
||
99 | 'currencyCode' => 'string(x:Amount/@currencyCode)', |
||
100 | 'billingFirstName' => 'string(x:BillingFirstName)', |
||
101 | 'billingLastName' => 'string(x:BillingLastName)', |
||
102 | 'billingPhone' => 'string(x:BillingPhoneNo)', |
||
103 | 'billingCity' => 'string(x:BillingAddress/x:City)', |
||
104 | 'billingCountryCode' => 'string(x:BillingAddress/x:CountryCode)', |
||
105 | 'customerEmail' => 'string(x:CustomerEmail)', |
||
106 | 'customerIpAddress' => 'string(x:CustomerIPAddress)', |
||
107 | 'shipToFirstName' => 'string(x:ShipToFirstName)', |
||
108 | 'shipToLastName' => 'string(x:ShipToLastName)', |
||
109 | 'shipToPhone' => 'string(x:ShipToPhoneNo)', |
||
110 | 'shipToCity' => 'string(x:ShippingAddress/x:City)', |
||
111 | 'shipToCountryCode' => 'string(x:ShippingAddress/x:CountryCode)', |
||
112 | 'isRequestToCorrectCVVOrAVSError' => 'boolean(x:isRequestToCorrectCVVOrAVSError)', |
||
113 | 'isEncrypted' => 'boolean(x:PaymentContext/x:EncryptedPaymentAccountUniqueId)', |
||
114 | ]; |
||
115 | $this->addressLinesExtractionMap = [ |
||
116 | [ |
||
117 | 'property' => 'billingLines', |
||
118 | 'xPath' => "x:BillingAddress/*[starts-with(name(), 'Line')]" |
||
119 | ], |
||
120 | [ |
||
121 | 'property' => 'shipToLines', |
||
122 | 'xPath' => "x:ShippingAddress/*[starts-with(name(), 'Line')]" |
||
123 | ] |
||
124 | ]; |
||
125 | $this->optionalExtractionPaths = [ |
||
126 | 'billingMainDivision' => 'x:BillingAddress/x:MainDivision', |
||
127 | 'billingPostalCode' => 'x:BillingAddress/x:PostalCode', |
||
128 | 'shipToMainDivision' => 'x:ShippingAddress/x:MainDivision', |
||
129 | 'shipToPostalCode' => 'x:ShippingAddress/x:PostalCode', |
||
130 | 'authenticationAvailable' => 'x:SecureVerificationData/x:AuthenticationAvailable', |
||
131 | 'authenticationStatus' => 'x:SecureVerificationData/x:AuthenticationStatus', |
||
132 | 'cavvUcaf' => 'x:SecureVerificationData/x:CavvUcaf', |
||
133 | 'transactionId' => 'x:SecureVerificationData/x:TransactionId', |
||
134 | 'payerAuthenticationResponse' => 'x:SecureVerificationData/x:PayerAuthenticationResponse', |
||
135 | 'eci' => 'x:SecureVerificationData/x:ECI', |
||
136 | ]; |
||
137 | $this->booleanExtractionPaths = [ |
||
138 | 'panIsToken' => 'string(x:PaymentContext/x:PaymentAccountUniqueId/@isToken)', |
||
139 | 'isRequestToCorrectCVVOrAVSError' => 'string(x:isRequestToCorrectCVVOrAVSError)' |
||
140 | ]; |
||
141 | } |
||
142 | |||
143 | public function setEmail($email) |
||
144 | { |
||
145 | $value = null; |
||
146 | $cleaned = $this->cleanString($email, 70); |
||
147 | if ($cleaned !== null) { |
||
148 | $match = filter_var($cleaned, FILTER_VALIDATE_EMAIL); |
||
149 | if ($match) { |
||
150 | $value = $cleaned; |
||
151 | } |
||
152 | } |
||
153 | $this->customerEmail = $value; |
||
154 | |||
155 | return $this; |
||
156 | } |
||
157 | |||
158 | public function setIp($ip) |
||
159 | { |
||
160 | $pattern = '/((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)/'; |
||
161 | $value = null; |
||
162 | |||
163 | $cleaned = $this->cleanString($ip, 70); |
||
164 | if ($cleaned !== null) { |
||
165 | $match = preg_match($pattern, $cleaned); |
||
166 | |||
167 | if ($match === 1) { |
||
168 | $value = $cleaned; |
||
169 | } |
||
170 | } |
||
171 | $this->customerIpAddress = $value; |
||
172 | |||
173 | return $this; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Serialize the various parts of the payload into XML strings and |
||
178 | * simply concatenate them together. |
||
179 | * @return string |
||
180 | */ |
||
181 | protected function serializeContents() |
||
182 | { |
||
183 | return $this->serializePaymentContext() |
||
184 | . $this->serializeCardInfo() |
||
185 | . $this->serializeBillingNamePhone() |
||
186 | . $this->serializeBillingAddress() |
||
187 | . $this->serializeCustomerInfo() |
||
188 | . $this->serializeShippingNamePhone() |
||
189 | . $this->serializeShippingAddress() |
||
190 | . $this->serializeIsCorrectError() |
||
191 | . $this->serializeSecureVerificationData(); |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Build the ExpirationDate, CardSecurityCode and Amount nodes |
||
196 | * |
||
197 | * @return string |
||
198 | */ |
||
199 | protected function serializeCardInfo() |
||
200 | { |
||
201 | return sprintf( |
||
202 | '<ExpirationDate>%s</ExpirationDate>%s<Amount currencyCode="%s">%.2f</Amount>', |
||
203 | $this->xmlEncode($this->getExpirationDate()), |
||
204 | $this->serializeCardSecurityCode(), |
||
205 | $this->xmlEncode($this->getCurrencyCode()), |
||
206 | $this->getAmount() |
||
207 | ); |
||
208 | } |
||
209 | |||
210 | public function getExpirationDate() |
||
214 | |||
215 | public function setExpirationDate(\DateTime $date) |
||
216 | { |
||
217 | $month = $date->format('j'); |
||
218 | $year = $date->format('Y'); |
||
219 | $this->expirationDate = checkdate($month, 1, $year) ? $date->format('Y-m') : null; |
||
220 | return $this; |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * Build the CardSecurityCode or, if the payload is using encrypted data, |
||
225 | * the EncryptedCardSecurityCode. |
||
226 | * |
||
227 | * @return string |
||
228 | */ |
||
229 | protected function serializeCardSecurityCode() |
||
230 | { |
||
231 | return sprintf( |
||
232 | '<%1$s>%2$s</%1$s>', |
||
233 | $this->getIsEncrypted() ? self::ENCRYPTED_CVV_NODE : self::RAW_CVV_NODE, |
||
234 | $this->xmlEncode($this->getCardSecurityCode()) |
||
235 | ); |
||
236 | } |
||
237 | |||
238 | public function getCardSecurityCode() |
||
242 | |||
243 | public function setCardSecurityCode($cvv) |
||
244 | { |
||
245 | if ($this->getIsEncrypted()) { |
||
246 | $this->cardSecurityCode = $this->cleanString($cvv, 1000); |
||
247 | } else { |
||
248 | $cleaned = $this->cleanString($cvv, 4); |
||
249 | $this->cardSecurityCode = preg_match('#^\d{3,4}$#', $cleaned) ? $cleaned : null; |
||
250 | } |
||
251 | return $this; |
||
252 | } |
||
253 | |||
254 | public function getCurrencyCode() |
||
258 | |||
259 | View Code Duplication | public function setCurrencyCode($code) |
|
260 | { |
||
261 | $value = null; |
||
262 | |||
263 | $cleaned = $this->cleanString($code, 3); |
||
264 | if ($cleaned !== null) { |
||
265 | if (!strlen($cleaned) < 3) { |
||
266 | $value = $cleaned; |
||
267 | } |
||
268 | } |
||
269 | $this->currencyCode = $value; |
||
270 | |||
271 | return $this; |
||
272 | } |
||
273 | |||
274 | public function getAmount() |
||
278 | |||
279 | View Code Duplication | public function setAmount($amount) |
|
280 | { |
||
281 | if (is_float($amount)) { |
||
282 | $this->amount = round($amount, 2, PHP_ROUND_HALF_UP); |
||
283 | } else { |
||
284 | $this->amount = null; |
||
285 | } |
||
286 | return $this; |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Build the BillingFirstName, BillingLastName and BillingPhoneNo nodes |
||
291 | * |
||
292 | * @return string |
||
293 | */ |
||
294 | protected function serializeBillingNamePhone() |
||
295 | { |
||
296 | $template = '<BillingFirstName>%s</BillingFirstName>' |
||
297 | . '<BillingLastName>%s</BillingLastName>' |
||
298 | . '<BillingPhoneNo>%s</BillingPhoneNo>'; |
||
299 | return sprintf( |
||
300 | $template, |
||
301 | $this->xmlEncode($this->getBillingFirstName()), |
||
302 | $this->xmlEncode($this->getBillingLastName()), |
||
303 | $this->xmlEncode($this->getBillingPhone()) |
||
304 | ); |
||
305 | } |
||
306 | |||
307 | public function getBillingFirstName() |
||
311 | |||
312 | View Code Duplication | public function setBillingFirstName($name) |
|
313 | { |
||
314 | $value = null; |
||
315 | |||
316 | if (is_string($name)) { |
||
317 | $trimmed = trim($name); |
||
318 | if (!empty($trimmed)) { |
||
319 | $value = $trimmed; |
||
320 | } |
||
321 | } |
||
322 | $this->billingFirstName = $value; |
||
323 | |||
324 | return $this; |
||
325 | } |
||
326 | |||
327 | public function getBillingLastName() |
||
331 | |||
332 | View Code Duplication | public function setBillingLastName($name) |
|
333 | { |
||
334 | $value = null; |
||
335 | |||
336 | if (is_string($name)) { |
||
337 | $trimmed = trim($name); |
||
338 | if (!empty($trimmed)) { |
||
339 | $value = $trimmed; |
||
340 | } |
||
341 | } |
||
342 | $this->billingLastName = $value; |
||
343 | |||
344 | return $this; |
||
345 | } |
||
346 | |||
347 | public function getBillingPhone() |
||
351 | |||
352 | View Code Duplication | public function setBillingPhone($phone) |
|
353 | { |
||
354 | $value = null; |
||
355 | |||
356 | if (is_string($phone)) { |
||
357 | $trimmed = trim($phone); |
||
358 | if (!empty($trimmed)) { |
||
359 | $value = $trimmed; |
||
360 | } |
||
361 | } |
||
362 | $this->billingPhone = $value; |
||
363 | |||
364 | return $this; |
||
365 | } |
||
366 | |||
367 | /** |
||
368 | * Build the CustomerEmail and CustomerIPAddress nodes |
||
369 | * |
||
370 | * @return string |
||
371 | */ |
||
372 | protected function serializeCustomerInfo() |
||
373 | { |
||
374 | return sprintf( |
||
375 | '<CustomerEmail>%s</CustomerEmail><CustomerIPAddress>%s</CustomerIPAddress>', |
||
376 | $this->xmlEncode($this->getEmail()), |
||
377 | $this->xmlEncode($this->getIp()) |
||
378 | ); |
||
379 | } |
||
380 | |||
381 | public function getEmail() |
||
385 | |||
386 | public function getIp() |
||
390 | |||
391 | /** |
||
392 | * Build the ShippingFirstName, ShippingLastName and ShippinggPhoneNo nodes |
||
393 | * |
||
394 | * @return string |
||
395 | */ |
||
396 | protected function serializeShippingNamePhone() |
||
397 | { |
||
398 | return sprintf( |
||
399 | '<ShipToFirstName>%s</ShipToFirstName><ShipToLastName>%s</ShipToLastName><ShipToPhoneNo>%s</ShipToPhoneNo>', |
||
400 | $this->xmlEncode($this->getShipToFirstName()), |
||
401 | $this->xmlEncode($this->getShipToLastName()), |
||
402 | $this->xmlEncode($this->getShipToPhone()) |
||
403 | ); |
||
404 | } |
||
405 | |||
406 | public function getShipToFirstName() |
||
410 | |||
411 | View Code Duplication | public function setShipToFirstName($name) |
|
412 | { |
||
413 | $value = null; |
||
414 | |||
415 | if (is_string($name)) { |
||
416 | $trimmed = trim($name); |
||
417 | if (!empty($trimmed)) { |
||
418 | $value = $trimmed; |
||
419 | } |
||
420 | } |
||
421 | $this->shipToFirstName = $value; |
||
422 | |||
423 | return $this; |
||
424 | } |
||
425 | |||
426 | public function getShipToLastName() |
||
430 | |||
431 | View Code Duplication | public function setShipToLastName($name) |
|
432 | { |
||
433 | $value = null; |
||
434 | |||
435 | if (is_string($name)) { |
||
436 | $trimmed = trim($name); |
||
437 | if (!empty($trimmed)) { |
||
438 | $value = $trimmed; |
||
439 | } |
||
440 | } |
||
441 | $this->shipToLastName = $value; |
||
442 | |||
443 | return $this; |
||
444 | } |
||
445 | |||
446 | public function getShipToPhone() |
||
450 | |||
451 | View Code Duplication | public function setShipToPhone($phone) |
|
452 | { |
||
453 | $value = null; |
||
454 | |||
455 | if (is_string($phone)) { |
||
456 | $trimmed = trim($phone); |
||
457 | if (!empty($trimmed)) { |
||
458 | $value = $trimmed; |
||
459 | } |
||
460 | } |
||
461 | $this->shipToPhone = $value; |
||
462 | |||
463 | return $this; |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * Build the isRequestToCorrectCVVOrAVSError node |
||
468 | * |
||
469 | * @return string |
||
470 | */ |
||
471 | protected function serializeIsCorrectError() |
||
472 | { |
||
473 | $string = sprintf( |
||
474 | '<isRequestToCorrectCVVOrAVSError>%s</isRequestToCorrectCVVOrAVSError>', |
||
475 | $this->getIsRequestToCorrectCvvOrAvsError() ? 'true' : 'false' |
||
476 | ); |
||
477 | return $string; |
||
478 | } |
||
479 | |||
480 | public function getIsRequestToCorrectCvvOrAvsError() |
||
484 | |||
485 | public function setIsRequestToCorrectCvvOrAvsError($flag) |
||
486 | { |
||
487 | $this->isRequestToCorrectCVVOrAVSError = is_bool($flag) ? $flag : null; |
||
488 | return $this; |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * Build the SecureVerificationData node |
||
493 | * |
||
494 | * @return string |
||
495 | */ |
||
496 | protected function serializeSecureVerificationData() |
||
497 | { |
||
498 | // make sure we have all of the required fields for this node |
||
499 | // if we don't then don't serialize it at all |
||
500 | if ($this->getAuthenticationAvailable() && |
||
501 | $this->getAuthenticationStatus() && |
||
502 | $this->getCavvUcaf() && |
||
503 | $this->getTransactionId() && |
||
504 | $this->getPayerAuthenticationResponse() |
||
505 | ) { |
||
506 | $template = '<SecureVerificationData>' |
||
507 | . '<AuthenticationAvailable>%s</AuthenticationAvailable>' |
||
508 | . '<AuthenticationStatus>%s</AuthenticationStatus>' |
||
509 | . '<CavvUcaf>%s</CavvUcaf>' |
||
510 | . '<TransactionId>%s</TransactionId>' |
||
511 | . '%s' |
||
512 | . '<PayerAuthenticationResponse>%s</PayerAuthenticationResponse>' |
||
513 | . '</SecureVerificationData>'; |
||
514 | return sprintf( |
||
515 | $template, |
||
516 | $this->xmlEncode($this->getAuthenticationAvailable()), |
||
517 | $this->xmlEncode($this->getAuthenticationStatus()), |
||
518 | $this->xmlEncode($this->getCavvUcaf()), |
||
519 | $this->xmlEncode($this->getTransactionId()), |
||
520 | $this->serializeOptionalXmlEncodedValue('ECI', $this->getEci()), |
||
521 | $this->xmlEncode($this->getPayerAuthenticationResponse()) |
||
522 | ); |
||
523 | } else { |
||
524 | return ''; |
||
525 | } |
||
526 | } |
||
527 | |||
528 | public function getAuthenticationAvailable() |
||
532 | |||
533 | View Code Duplication | public function setAuthenticationAvailable($token) |
|
534 | { |
||
535 | $value = null; |
||
536 | |||
537 | $cleaned = $this->cleanString($token, 1); |
||
538 | if ($cleaned !== null) { |
||
539 | $cleaned = strtoupper($cleaned); |
||
540 | if (strstr('YNU', $cleaned)) { |
||
541 | $value = $cleaned; |
||
542 | } |
||
543 | } |
||
544 | $this->authenticationAvailable = $value; |
||
545 | |||
546 | return $this; |
||
547 | } |
||
548 | |||
549 | public function getAuthenticationStatus() |
||
553 | |||
554 | View Code Duplication | public function setAuthenticationStatus($token) |
|
555 | { |
||
556 | $value = null; |
||
557 | |||
558 | $cleaned = $this->cleanString($token, 1); |
||
559 | if ($cleaned !== null) { |
||
560 | $cleaned = strtoupper($cleaned); |
||
561 | if (strstr('YNUA', $cleaned)) { |
||
562 | $value = $cleaned; |
||
563 | } |
||
564 | } |
||
565 | $this->authenticationStatus = $value; |
||
566 | |||
567 | return $this; |
||
568 | } |
||
569 | |||
570 | public function getCavvUcaf() |
||
574 | |||
575 | public function setCavvUcaf($data) |
||
576 | { |
||
577 | $this->cavvUcaf = $this->cleanString($data, 64); |
||
578 | return $this; |
||
579 | } |
||
580 | |||
581 | public function getTransactionId() |
||
585 | |||
586 | public function setTransactionId($id) |
||
587 | { |
||
588 | $this->transactionId = $this->cleanString($id, 64); |
||
589 | return $this; |
||
590 | } |
||
591 | |||
592 | public function getPayerAuthenticationResponse() |
||
596 | |||
597 | public function setPayerAuthenticationResponse($response) |
||
598 | { |
||
599 | $this->payerAuthenticationResponse = $this->cleanString($response, 10000); |
||
600 | return $this; |
||
601 | } |
||
602 | |||
603 | public function getEci() |
||
607 | |||
608 | View Code Duplication | public function setEci($eci) |
|
609 | { |
||
610 | $value = null; |
||
611 | |||
612 | if (is_string($eci)) { |
||
613 | $trimmed = trim($eci); |
||
614 | if (!empty($trimmed)) { |
||
615 | $value = $trimmed; |
||
616 | } |
||
617 | } |
||
618 | $this->eci = $value; |
||
619 | |||
620 | return $this; |
||
621 | } |
||
622 | |||
623 | /** |
||
624 | * Name, value pairs of root attributes |
||
625 | * |
||
626 | * @return array |
||
627 | */ |
||
628 | protected function getRootAttributes() |
||
629 | { |
||
630 | return [ |
||
631 | 'xmlns' => $this->getXmlNamespace(), |
||
632 | 'requestId' => $this->xmlEncode($this->getRequestId()), |
||
633 | ]; |
||
634 | } |
||
635 | |||
636 | /** |
||
637 | * The XML namespace for the payload. |
||
638 | * |
||
639 | * @return string |
||
640 | */ |
||
641 | protected function getXmlNamespace() |
||
645 | |||
646 | public function getRequestId() |
||
650 | |||
651 | public function setRequestId($requestId) |
||
652 | { |
||
653 | $this->requestId = $this->cleanString($requestId, 40); |
||
654 | return $this; |
||
655 | } |
||
656 | |||
657 | protected function getSchemaFile() |
||
661 | |||
662 | /** |
||
663 | * Return the name of the xml root node. |
||
664 | * |
||
665 | * @return string |
||
666 | */ |
||
667 | protected function getRootNodeName() |
||
671 | } |
||
672 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.