Completed
Pull Request — master (#650)
by Jeroen De
19:07 queued 01:42
created

MembershipApplicationValidator   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 8
dl 0
loc 210
rs 8.295
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A validate() 0 13 1
A validateFee() 0 9 1
A getApplicantType() 0 4 2
A addViolations() 0 3 1
A validateBankData() 0 14 2
B getBankDataViolationSource() 0 16 6
B getBankDataViolationType() 0 14 5
A validateApplicantDateOfBirth() 0 7 3
A validateApplicantContactInfo() 0 8 2
A validatePhoneNumber() 0 8 3
A validateApplicantName() 0 8 2
A validateCompanyName() 0 6 2
A validatePersonName() 0 16 4
B validateApplicantAddress() 0 24 5
A validateFieldLength() 0 5 2

How to fix   Complexity   

Complex Class

Complex classes like MembershipApplicationValidator 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 MembershipApplicationValidator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\MembershipContext\UseCases\ApplyForMembership;
6
7
use WMDE\Fundraising\Frontend\MembershipContext\UseCases\ApplyForMembership\ApplicationValidationResult as Result;
8
use WMDE\Fundraising\Frontend\Validation\BankDataValidator;
9
use WMDE\Fundraising\Frontend\Validation\ConstraintViolation;
10
use WMDE\Fundraising\Frontend\Validation\EmailValidator;
11
use WMDE\Fundraising\Frontend\Validation\MembershipFeeValidator as FeeValidator;
12
13
/**
14
 * @license GNU GPL v2+
15
 * @author Jeroen De Dauw < [email protected] >
16
 */
17
class MembershipApplicationValidator {
18
19
	private $feeValidator;
20
	private $bankDataValidator;
21
	private $emailValidator;
22
23
	/**
24
	 * @var ApplyForMembershipRequest
25
	 */
26
	private $request;
27
28
	/**
29
	 * @var string[] ApplicationValidationResult::SOURCE_ => ApplicationValidationResult::VIOLATION_
30
	 */
31
	private $violations;
32
33
	private $maximumFieldLengths = [
34
		Result::SOURCE_APPLICANT_PHONE_NUMBER => 30,
35
		Result::SOURCE_APPLICANT_EMAIL => 250,
36
		Result::SOURCE_APPLICANT_COMPANY => 100,
37
		Result::SOURCE_APPLICANT_FIRST_NAME => 50,
38
		Result::SOURCE_APPLICANT_LAST_NAME => 50,
39
		Result::SOURCE_APPLICANT_SALUTATION => 16,
40
		Result::SOURCE_APPLICANT_STREET_ADDRESS => 100,
41
		Result::SOURCE_APPLICANT_POSTAL_CODE => 8,
42
		Result::SOURCE_APPLICANT_CITY => 100,
43
		Result::SOURCE_APPLICANT_COUNTRY => 8,
44
		Result::SOURCE_BANK_NAME => 50,
45
		Result::SOURCE_BIC => 32,
46
	];
47
48
	public function __construct( FeeValidator $feeValidator, BankDataValidator $bankDataValidator,
49
		EmailValidator $emailValidator ) {
50
51
		$this->feeValidator = $feeValidator;
52
		$this->bankDataValidator = $bankDataValidator;
53
		$this->emailValidator = $emailValidator;
54
	}
55
56
	public function validate( ApplyForMembershipRequest $applicationRequest ): Result {
57
		$this->request = $applicationRequest;
58
		$this->violations = [];
59
60
		$this->validateFee();
61
		$this->validateBankData();
62
		$this->validateApplicantName();
63
		$this->validateApplicantContactInfo();
64
		$this->validateApplicantDateOfBirth();
65
		$this->validateApplicantAddress();
66
67
		return new Result( $this->violations );
68
	}
69
70
	private function validateFee() {
71
		$result = $this->feeValidator->validate(
72
			$this->request->getPaymentAmountInEuros(),
73
			$this->request->getPaymentIntervalInMonths(),
74
			$this->getApplicantType()
75
		);
76
77
		$this->addViolations( $result->getViolations() );
78
	}
79
80
	private function getApplicantType() {
81
		return $this->request->isCompanyApplication() ?
82
			FeeValidator::APPLICANT_TYPE_COMPANY : FeeValidator::APPLICANT_TYPE_PERSON;
83
	}
84
85
	private function addViolations( array $violations ) {
86
		$this->violations = array_merge( $this->violations, $violations );
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->violations, $violations) of type array is incompatible with the declared type array<integer,string> of property $violations.

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..

Loading history...
87
	}
88
89
	private function validateBankData() {
90
		$bankData = $this->request->getPaymentBankData();
91
		$validationResult = $this->bankDataValidator->validate( $bankData );
92
		$violations = [];
93
94
		$this->validateFieldLength( $bankData->getBankName(), Result::SOURCE_BANK_NAME );
95
		$this->validateFieldLength( $bankData->getBic(), Result::SOURCE_BIC );
96
97
		foreach ( $validationResult->getViolations() as $violation ) {
98
			$violations[$this->getBankDataViolationSource( $violation )] = $this->getBankDataViolationType( $violation );
99
		}
100
101
		$this->addViolations( $violations );
102
	}
103
104
	private function getBankDataViolationSource( ConstraintViolation $violation ): string {
105
		switch ( $violation->getSource() ) {
106
			case 'iban':
107
				return Result::SOURCE_IBAN;
108
			case 'bic':
109
				return Result::SOURCE_BIC;
110
			case 'bankname':
111
				return Result::SOURCE_BANK_NAME;
112
			case 'blz':
113
				return Result::SOURCE_BANK_CODE;
114
			case 'konto':
115
				return Result::SOURCE_BANK_ACCOUNT;
116
			default:
117
				throw new \LogicException();
118
		}
119
	}
120
121
	private function getBankDataViolationType( ConstraintViolation $violation ): string {
122
		switch ( $violation->getMessageIdentifier() ) {
123
			case 'field_required':
124
				return Result::VIOLATION_MISSING;
125
			case 'incorrect_length':
126
				return Result::VIOLATION_WRONG_LENGTH;
127
			case 'iban_blocked':
128
				return Result::VIOLATION_IBAN_BLOCKED;
129
			case 'iban_invalid':
130
				return Result::VIOLATION_IBAN_INVALID;
131
			default:
132
				throw new \LogicException();
133
		}
134
	}
135
136
	private function validateApplicantDateOfBirth() {
137
		$dob = $this->request->getApplicantDateOfBirth();
138
139
		if ( $dob !== '' && !strtotime( $dob ) ) {
140
			$this->violations[Result::SOURCE_APPLICANT_DATE_OF_BIRTH] = Result::VIOLATION_NOT_DATE;
141
		}
142
	}
143
144
	private function validateApplicantContactInfo() {
145
		$this->validatePhoneNumber();
146
147
		$this->validateFieldLength( $this->request->getApplicantEmailAddress(), Result::SOURCE_APPLICANT_EMAIL );
148
		if ( $this->emailValidator->validate( $this->request->getApplicantEmailAddress() )->hasViolations() ) {
149
			$this->violations[Result::SOURCE_APPLICANT_EMAIL] = Result::VIOLATION_NOT_EMAIL;
150
		}
151
	}
152
153
	private function validatePhoneNumber() {
154
		$phoneNumber = $this->request->getApplicantPhoneNumber();
155
156
		$this->validateFieldLength( $phoneNumber, Result::SOURCE_APPLICANT_PHONE_NUMBER );
157
		if ( $phoneNumber !== '' && !preg_match( '/^[0-9\+\-\(\)]+/i', $phoneNumber ) ) {
158
			$this->violations[Result::SOURCE_APPLICANT_PHONE_NUMBER] = Result::VIOLATION_NOT_PHONE_NUMBER;
159
		}
160
	}
161
162
	private function validateApplicantName() {
163
		if ( $this->request->isCompanyApplication() ) {
164
			$this->validateCompanyName();
165
		}
166
		else {
167
			$this->validatePersonName();
168
		}
169
	}
170
171
	private function validateCompanyName() {
172
		if ( $this->request->getApplicantCompanyName() === '' ) {
173
			$this->violations[Result::SOURCE_APPLICANT_COMPANY] = Result::VIOLATION_MISSING;
174
		}
175
		$this->validateFieldLength( $this->request->getApplicantCompanyName(), Result::SOURCE_APPLICANT_COMPANY );
176
	}
177
178
	private function validatePersonName() {
179
		if ( $this->request->getApplicantFirstName() === '' ) {
180
			$this->violations[Result::SOURCE_APPLICANT_FIRST_NAME] = Result::VIOLATION_MISSING;
181
		}
182
		$this->validateFieldLength( $this->request->getApplicantFirstName(), Result::SOURCE_APPLICANT_FIRST_NAME );
183
184
		if ( $this->request->getApplicantLastName() === '' ) {
185
			$this->violations[Result::SOURCE_APPLICANT_LAST_NAME] = Result::VIOLATION_MISSING;
186
		}
187
		$this->validateFieldLength( $this->request->getApplicantLastName(), Result::SOURCE_APPLICANT_LAST_NAME );
188
189
		if ( $this->request->getApplicantSalutation() === '' ) {
190
			$this->violations[Result::SOURCE_APPLICANT_SALUTATION] = Result::VIOLATION_MISSING;
191
		}
192
		$this->validateFieldLength( $this->request->getApplicantSalutation(), Result::SOURCE_APPLICANT_SALUTATION );
193
	}
194
195
	private function validateApplicantAddress() {
196
		if ( $this->request->getApplicantStreetAddress() === '' ) {
197
			$this->violations[Result::SOURCE_APPLICANT_STREET_ADDRESS] = Result::VIOLATION_MISSING;
198
		}
199
		$this->validateFieldLength(
200
			$this->request->getApplicantStreetAddress(),
201
			Result::SOURCE_APPLICANT_STREET_ADDRESS
202
		);
203
204
		if ( $this->request->getApplicantPostalCode() === '' ) {
205
			$this->violations[Result::SOURCE_APPLICANT_POSTAL_CODE] = Result::VIOLATION_MISSING;
206
		}
207
		$this->validateFieldLength( $this->request->getApplicantPostalCode(), Result::SOURCE_APPLICANT_POSTAL_CODE );
208
209
		if ( $this->request->getApplicantCity() === '' ) {
210
			$this->violations[Result::SOURCE_APPLICANT_CITY] = Result::VIOLATION_MISSING;
211
		}
212
		$this->validateFieldLength( $this->request->getApplicantCity(), Result::SOURCE_APPLICANT_CITY );
213
214
		if ( $this->request->getApplicantCountryCode() === '' ) {
215
			$this->violations[Result::SOURCE_APPLICANT_COUNTRY] = Result::VIOLATION_MISSING;
216
		}
217
		$this->validateFieldLength( $this->request->getApplicantCountryCode(), Result::SOURCE_APPLICANT_COUNTRY );
218
	}
219
220
	private function validateFieldLength( string $value, string $fieldName ) {
221
		if ( strlen( $value ) > $this->maximumFieldLengths[$fieldName] )  {
222
			$this->violations[$fieldName] = Result::VIOLATION_WRONG_LENGTH;
223
		}
224
	}
225
226
}