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 PaymentRepository 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 PaymentRepository, and based on these observations, apply Extract Interface, too.
1 | <?php namespace BB\Repo; |
||
9 | class PaymentRepository extends DBRepository |
||
10 | { |
||
11 | |||
12 | /** |
||
13 | * @var Payment |
||
14 | */ |
||
15 | protected $model; |
||
16 | |||
17 | public static $SUBSCRIPTION = 'subscription'; |
||
18 | public static $INDUCTION = 'induction'; |
||
19 | |||
20 | private $reason = null; |
||
21 | private $source = null; |
||
22 | |||
23 | /** |
||
24 | * @param Payment $model |
||
25 | */ |
||
26 | public function __construct(Payment $model) |
||
31 | |||
32 | |||
33 | public function getPaginated(array $params) |
||
58 | |||
59 | |||
60 | public function getTotalAmount() |
||
82 | |||
83 | |||
84 | /** |
||
85 | * Record a payment against a user record |
||
86 | * |
||
87 | * @param string $reason What was the reason. subscription, induction, etc... |
||
88 | * @param int $userId The users ID |
||
89 | * @param string $source gocardless, paypal |
||
90 | * @param string $sourceId A reference for the source |
||
91 | * @param double $amount Amount received before a fee in pounds |
||
92 | * @param string $status paid, pending, cancelled, refunded |
||
93 | * @param double $fee The fee charged by the payment provider |
||
94 | * @param string $ref |
||
95 | * @param Carbon $paidDate |
||
96 | * @return int The ID of the payment record |
||
97 | */ |
||
98 | public function recordPayment($reason, $userId, $source, $sourceId, $amount, $status = 'paid', $fee = 0.0, $ref = '', Carbon $paidDate = null) |
||
129 | |||
130 | /** |
||
131 | * Record a subscription payment |
||
132 | * |
||
133 | * @param int $userId The users ID |
||
134 | * @param string $source gocardless, paypal |
||
135 | * @param string $sourceId A reference for the source |
||
136 | * @param double $amount Amount received before a fee in pounds |
||
137 | * @param string $status paid, pending, cancelled, refunded |
||
138 | * @param double $fee The fee charged by the payment provider |
||
139 | * @param string|null $ref |
||
140 | * @param Carbon $paidDate |
||
141 | * @return int The ID of the payment record |
||
142 | */ |
||
143 | public function recordSubscriptionPayment($userId, $source, $sourceId, $amount, $status = 'paid', $fee = 0.0, $ref = '', Carbon $paidDate = null) |
||
147 | |||
148 | /** |
||
149 | * An existing payment has been set to paid |
||
150 | * |
||
151 | * @param $paymentId |
||
152 | * @param Carbon $paidDate |
||
153 | */ |
||
154 | public function markPaymentPaid($paymentId, $paidDate) |
||
163 | |||
164 | /** |
||
165 | * An existing payment has been set to pending/submitted |
||
166 | * |
||
167 | * @param $paymentId |
||
168 | */ |
||
169 | public function markPaymentPending($paymentId) |
||
175 | |||
176 | /** |
||
177 | * Record a payment failure or cancellation |
||
178 | * |
||
179 | * @param int $paymentId |
||
180 | * @param string $status |
||
181 | */ |
||
182 | public function recordPaymentFailure($paymentId, $status = 'failed') |
||
190 | |||
191 | /** |
||
192 | * Assign an unassigned payment to a user |
||
193 | * |
||
194 | * @param int $paymentId |
||
195 | * @param int $userId |
||
196 | * |
||
197 | * @throws PaymentException |
||
198 | */ |
||
199 | public function assignPaymentToUser($paymentId, $userId) |
||
209 | |||
210 | /** |
||
211 | * Take a payment that has been used for something and reassign it to the balance |
||
212 | * @param $paymentId |
||
213 | * |
||
214 | * @throws NotImplementedException |
||
215 | */ |
||
216 | public function refundPaymentToBalance($paymentId) |
||
236 | |||
237 | |||
238 | /** |
||
239 | * Fetch the users latest payment of a particular type |
||
240 | * @param integer $userId |
||
241 | * @param string $reason |
||
242 | * @return mixed |
||
243 | */ |
||
244 | View Code Duplication | public function latestUserPayment($userId, $reason = 'subscription') |
|
251 | |||
252 | |||
253 | /** |
||
254 | * Get all user payments of a specific reason |
||
255 | * @param $userId |
||
256 | * @param string $reason |
||
257 | * @return mixed |
||
258 | */ |
||
259 | View Code Duplication | public function getUserPaymentsByReason($userId, $reason) |
|
266 | |||
267 | |||
268 | /** |
||
269 | * @param string $source |
||
270 | */ |
||
271 | View Code Duplication | public function getUserPaymentsBySource($userId, $source) |
|
278 | |||
279 | /** |
||
280 | * Get all payments with a specific reference |
||
281 | * @param string $reference |
||
282 | * @return \Illuminate\Database\Eloquent\Collection |
||
283 | */ |
||
284 | public function getPaymentsByReference($reference) |
||
288 | |||
289 | /** |
||
290 | * @param string $referencePrefix |
||
291 | * @return \Illuminate\Database\Eloquent\Collection |
||
292 | */ |
||
293 | public function getEquipmentFeePayments($referencePrefix) |
||
299 | |||
300 | |||
301 | /** |
||
302 | * Return a paginated list of balance affecting payment for a user |
||
303 | * @param $userId |
||
304 | * @return \Illuminate\Database\Eloquent\Collection |
||
305 | */ |
||
306 | View Code Duplication | public function getBalancePaymentsPaginated($userId) |
|
313 | |||
314 | |||
315 | /** |
||
316 | * Return a collection of payments specifically for storage boxes |
||
317 | * @param integer $userId |
||
318 | * @return mixed |
||
319 | */ |
||
320 | View Code Duplication | public function getStorageBoxPayments($userId) |
|
327 | |||
328 | public function dateFilter($startDate, $endDate) |
||
333 | |||
334 | private function hasDateFilter() |
||
338 | |||
339 | /** |
||
340 | * Delete a record |
||
341 | * @param $recordId |
||
342 | * @return bool|null |
||
343 | * @throws \Exception |
||
344 | */ |
||
345 | public function delete($recordId) |
||
356 | |||
357 | public function canDelete($recordId) |
||
361 | |||
362 | /** |
||
363 | * Used for the getPaginated and getTotalAmount method |
||
364 | * @param $reasonFilter |
||
365 | */ |
||
366 | public function reasonFilter($reasonFilter) |
||
370 | |||
371 | private function hasReasonFilter() |
||
375 | |||
376 | /** |
||
377 | * Used for the getPaginated and getTotalAmount method |
||
378 | * @param string $sourceFilter |
||
379 | */ |
||
380 | public function sourceFilter($sourceFilter) |
||
384 | |||
385 | |||
386 | private function hasSourceFilter() |
||
390 | |||
391 | /** |
||
392 | * Used for the getPaginated and getTotalAmount method |
||
393 | */ |
||
394 | public function resetFilters() |
||
402 | |||
403 | /** |
||
404 | * Fetch a payment record using the id provided by the payment provider |
||
405 | * |
||
406 | * @param $sourceId |
||
407 | * @return Payment |
||
408 | */ |
||
409 | public function getPaymentBySourceId($sourceId) |
||
413 | |||
414 | /** |
||
415 | * Record a balance payment transfer between two users |
||
416 | * |
||
417 | * @param integer $sourceUserId |
||
418 | * @param integer $targetUserId |
||
419 | * @param double $amount |
||
420 | */ |
||
421 | public function recordBalanceTransfer($sourceUserId, $targetUserId, $amount) |
||
431 | } |
||
432 |
If you implement
__call
and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.This is often the case, when
__call
is implemented by a parent class and only the child class knows which methods exist: