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

Donation   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
wmc 43
lcom 2
cbo 5
dl 0
loc 223
rs 8.3157
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A getId() 0 3 1
A assignId() 0 7 3
A getStatus() 0 3 1
A getAmount() 0 3 1
A getPaymentIntervalInMonths() 0 3 1
A getPaymentType() 0 3 1
A getDonor() 0 3 1
A getComment() 0 3 1
A addComment() 0 7 2
A getPayment() 0 3 1
A getPaymentMethod() 0 3 1
A getOptsIntoNewsletter() 0 3 1
A cancel() 0 11 3
B confirmBooked() 0 15 6
A makeCommentPrivate() 0 7 1
A hasComment() 0 3 1
A statusAllowsForBooking() 0 3 3
A markForModeration() 0 3 1
A getTrackingInfo() 0 3 1
A addPayPalData() 0 9 2
A addCreditCardData() 0 9 2
A statusIsCancellable() 0 3 2
A hasExternalPayment() 0 3 1
A isIncomplete() 0 3 1
A needsModeration() 0 3 1
A isBooked() 0 3 1
A isCancelled() 0 3 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare( strict_types = 1 );
4
5
namespace WMDE\Fundraising\Frontend\DonationContext\Domain\Model;
6
7
use RuntimeException;
8
use WMDE\Euro\Euro;
9
use WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\CreditCardPayment;
10
use WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\CreditCardTransactionData;
11
use WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PaymentMethod;
12
use WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PaymentType;
13
use WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalData;
14
use WMDE\Fundraising\Frontend\PaymentContext\Domain\Model\PayPalPayment;
15
16
/**
17
 * @licence GNU GPL v2+
18
 * @author Kai Nissen < [email protected] >
19
 * @author Jeroen De Dauw < [email protected] >
20
 */
21
class Donation {
22
23
	const STATUS_NEW = 'N'; // status for direct debit
24
	const STATUS_PROMISE = 'Z'; // status for bank transfer
25
	const STATUS_EXTERNAL_INCOMPLETE = 'X'; // status for external payments
26
	const STATUS_EXTERNAL_BOOKED = 'B'; // status for external payments
27
	const STATUS_MODERATION = 'P';
28
	const STATUS_CANCELLED = 'D';
29
30
	const OPTS_INTO_NEWSLETTER = true;
31
	const DOES_NOT_OPT_INTO_NEWSLETTER = false;
32
33
	const NO_APPLICANT = null;
34
35
	private $id;
36
	private $status;
37
	private $donor;
38
	private $payment;
39
	private $optsIntoNewsletter;
40
	private $comment;
41
42
	/**
43
	 * TODO: move out of Donation
44
	 */
45
	private $trackingInfo;
46
47
	public function __construct( int $id = null, string $status, Donor $donor = null, DonationPayment $payment,
48
		bool $optsIntoNewsletter, DonationTrackingInfo $trackingInfo, DonationComment $comment = null ) {
49
50
		$this->id = $id;
51
		$this->status = $status;
52
		$this->donor = $donor;
53
		$this->payment = $payment;
54
		$this->optsIntoNewsletter = $optsIntoNewsletter;
55
		$this->trackingInfo = $trackingInfo;
56
		$this->comment = $comment;
57
	}
58
59
	/**
60
	 * @return int|null
61
	 */
62
	public function getId() {
63
		return $this->id;
64
	}
65
66
	/**
67
	 * @param int $id
68
	 * @throws \RuntimeException
69
	 */
70
	public function assignId( int $id ) {
71
		if ( $this->id !== null && $this->id !== $id ) {
72
			throw new \RuntimeException( 'Id cannot be changed after initial assignment' );
73
		}
74
75
		$this->id = $id;
76
	}
77
78
	public function getStatus(): string {
79
		return $this->status;
80
	}
81
82
	public function getAmount(): Euro {
83
		return $this->payment->getAmount();
84
	}
85
86
	public function getPaymentIntervalInMonths(): int {
87
		return $this->payment->getIntervalInMonths();
88
	}
89
90
	public function getPaymentType(): string {
91
		return $this->payment->getPaymentMethod()->getType();
92
	}
93
94
	/**
95
	 * Returns the Donor or null for anonymous donations.
96
	 *
97
	 * @return Donor|null
98
	 */
99
	public function getDonor() {
100
		return $this->donor;
101
	}
102
103
	/**
104
	 * Returns the DonationComment or null for when there is none.
105
	 *
106
	 * @return DonationComment|null
107
	 */
108
	public function getComment() {
109
		return $this->comment;
110
	}
111
112
	public function addComment( DonationComment $comment ) {
113
		if ( $this->hasComment() ) {
114
			throw new RuntimeException( 'Can only add a single comment to a donation' );
115
		}
116
117
		$this->comment = $comment;
118
	}
119
120
	public function getPayment(): DonationPayment {
121
		return $this->payment;
122
	}
123
124
	public function getPaymentMethod(): PaymentMethod {
125
		return $this->payment->getPaymentMethod();
126
	}
127
128
	public function getOptsIntoNewsletter(): bool {
129
		return $this->optsIntoNewsletter;
130
	}
131
132
	/**
133
	 * @throws RuntimeException
134
	 */
135
	public function cancel() {
136
		if ( $this->getPaymentType() !== PaymentType::DIRECT_DEBIT ) {
137
			throw new RuntimeException( 'Can only cancel direct debit' );
138
		}
139
140
		if ( !$this->statusIsCancellable() ) {
141
			throw new RuntimeException( 'Can only cancel new donations' );
142
		}
143
144
		$this->status = self::STATUS_CANCELLED;
145
	}
146
147
	/**
148
	 * @throws RuntimeException
149
	 */
150
	public function confirmBooked() {
151
		if ( !$this->hasExternalPayment() ) {
152
			throw new RuntimeException( 'Only external payments can be confirmed as booked' );
153
		}
154
155
		if ( !$this->statusAllowsForBooking() ) {
156
			throw new RuntimeException( 'Only incomplete donations can be confirmed as booked' );
157
		}
158
159
		if ( $this->hasComment() && ( $this->needsModeration() || $this->isCancelled() ) ) {
160
			$this->makeCommentPrivate();
161
		}
162
163
		$this->status = self::STATUS_EXTERNAL_BOOKED;
164
	}
165
166
	private function makeCommentPrivate() {
167
		$this->comment = new DonationComment(
168
			$this->comment->getCommentText(),
169
			false,
170
			$this->comment->getAuthorDisplayName()
171
		);
172
	}
173
174
	public function hasComment(): bool {
175
		return $this->comment !== null;
176
	}
177
178
	private function statusAllowsForBooking(): bool {
179
		return $this->isIncomplete() || $this->needsModeration() || $this->isCancelled();
180
	}
181
182
	public function markForModeration() {
183
		$this->status = self::STATUS_MODERATION;
184
	}
185
186
	public function getTrackingInfo(): DonationTrackingInfo {
187
		return $this->trackingInfo;
188
	}
189
190
	/**
191
	 * @param PayPalData $payPalData
192
	 * @throws RuntimeException
193
	 */
194
	public function addPayPalData( PayPalData $payPalData ) {
195
		$paymentMethod = $this->payment->getPaymentMethod();
196
197
		if ( !( $paymentMethod instanceof PayPalPayment ) ) {
198
			throw new RuntimeException( 'Cannot set PayPal data on a non PayPal payment' );
199
		}
200
201
		$paymentMethod->addPayPalData( $payPalData );
202
	}
203
204
	/**
205
	 * @param CreditCardTransactionData $creditCardData
206
	 *
207
	 * @throws RuntimeException
208
	 */
209
	public function addCreditCardData( CreditCardTransactionData $creditCardData ) {
210
		$paymentMethod = $this->payment->getPaymentMethod();
211
212
		if ( !( $paymentMethod instanceof CreditCardPayment ) ) {
213
			throw new RuntimeException( 'Cannot set credit card transaction data on a non credit card payment' );
214
		}
215
216
		$paymentMethod->addCreditCardTransactionData( $creditCardData );
217
	}
218
219
	private function statusIsCancellable(): bool {
220
		return $this->status === self::STATUS_NEW || $this->status === self::STATUS_MODERATION;
221
	}
222
223
	public function hasExternalPayment(): bool {
224
		return in_array( $this->getPaymentType(), [ PaymentType::PAYPAL, PaymentType::CREDIT_CARD ] );
225
	}
226
227
	private function isIncomplete(): bool {
228
		return $this->status === self::STATUS_EXTERNAL_INCOMPLETE;
229
	}
230
231
	public function needsModeration(): bool {
232
		return $this->status === self::STATUS_MODERATION;
233
	}
234
235
	public function isBooked(): bool {
236
		return $this->status === self::STATUS_EXTERNAL_BOOKED;
237
	}
238
239
	public function isCancelled(): bool {
240
		return $this->status === self::STATUS_CANCELLED;
241
	}
242
243
}
244