Member   F
last analyzed

Complexity

Total Complexity 102

Size/Duplication

Total Lines 581
Duplicated Lines 0 %

Test Coverage

Coverage 37.4%

Importance

Changes 0
Metric Value
eloc 267
dl 0
loc 581
rs 2
c 0
b 0
f 0
ccs 98
cts 262
cp 0.374
wmc 102

22 Methods

Rating   Name   Duplication   Size   Complexity  
A offices() 0 16 4
A cohortPartyComparisonDirection() 0 29 3
A currentPartyComparison() 0 8 2
A image() 0 20 3
A cohortParty() 0 33 5
A isNew() 0 14 4
A getOtherConstituenciesString() 0 6 2
A getOfficeObject() 0 14 4
A getRegionalList() 0 45 5
A left_house_line() 0 13 5
B getEnterLeaveStrings() 0 41 8
C calculatePolicyDiffScore() 0 17 13
A isDead() 0 36 6
A getMostRecentMembership() 0 24 3
A includeOffice() 0 14 5
A entered_house_line() 0 13 4
B getRepNameForHouse() 0 27 8
B getPartyPolicyDiffs() 0 39 7
A getEntryDate() 0 10 2
A getOtherPartiesString() 0 12 5
A getLeftDate() 0 10 2
A getEUStance() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like Member 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.

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 Member, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Member Class
4
 *
5
 * @package TheyWorkForYou
6
 */
7
8
namespace MySociety\TheyWorkForYou;
9
10
/**
11
 * Member
12
 */
13
14
class Member extends \MEMBER {
15
    /**
16
     * Is Dead
17
     *
18
     * Determine if the member has died or not.
19
     *
20
     * @return boolean If the member is dead or not.
21
     */
22
23
    public function isDead() {
24
25
        $left_house = $this->left_house();
26
27
        if ($left_house) {
28
29
            // This member has left a house, and might be dead. See if they are.
30
31
            // Types of house to test for death.
32
            $house_types = [
33
                HOUSE_TYPE_COMMONS,
34
                HOUSE_TYPE_LORDS,
35
                HOUSE_TYPE_SCOTLAND,
36
                HOUSE_TYPE_NI,
37
                HOUSE_TYPE_WALES,
38
                HOUSE_TYPE_LONDON_ASSEMBLY,
39
            ];
40
41
            foreach ($house_types as $house_type) {
42
43
                if (in_array($house_type, $left_house) and
44
                    $left_house[$house_type]['reason'] and
45
                    $left_house[$house_type]['reason'] == 'Died'
46
                ) {
47
48
                    // This member has left a house because of death.
49
                    return true;
50
                }
51
52
            }
53
54
        }
55
56
        // If we get this far the member hasn't left a house due to death, and
57
        // is presumably alive.
58
        return false;
59
60
    }
61
62
    public function cohortPartyComparisonDirection() {
63
        // Is this MP and their cohort compared against the
64
        // first or last party they have?
65
        // By default, mirroring the partycohort query,
66
        // this is the first party.
67
        // However, this is ignored for the Speaker, and additional
68
        // individual overrides can be added below.
69
        // This makes most sense when a party switch was a long time ago.
70
        // As long as this person is in a cohort of 1 and has a unique set
71
        // of memberships (which is likely for edge cases) it doesn't matter
72 13
        // that their original party is what is used when in the party cohort
73
        // construction query.
74 13
75 13
        $person_id = $this->person_id();
76
77
        $direction = "first";
78 20
        if ($this->party() == "Speaker") {
79
            $direction = "last";
80
        }
81
82
        // MPs who have switched parties but should be compared against their
83
        // current party can go here.
84
        $use_last_party = [10172, 14031, 25873, 10218];
85
86
        if (in_array($person_id, $use_last_party)) {
87
            $direction = "last";
88
        }
89
90
        return $direction;
91 20
    }
92
93 20
    public function currentPartyComparison() {
94 20
        # Simplify the current party when being compared to the original
95 20
        # Stops co-op and labour being seen as different
96
        $party = $this->party;
97
        if ($party == 'Labour/Co-operative') {
98
            $party = 'Labour';
99
        }
100 20
        return $party;
101
    }
102 20
103 1
    public function cohortParty($house = HOUSE_TYPE_COMMONS) {
104
        // The party being compared against for party comparison purposes
105
        // Unless specified by the condition in cohortPartyComparisonDirection
106 20
        // This is the first, not last, party a person has.
107
108
        $person_id = $this->person_id();
109
        $db = new \ParlDB();
110
111
        $cohort_direction = $this->cohortPartyComparisonDirection();
112
113
        if ($cohort_direction == "first") {
114
            $direction = " ASC";
115
        } else {
116
            $direction = " DESC";
117
        }
118
119 20
        $row = $db->query("SELECT party from member
120
        where house = :house
121
        and person_id = :person_id
122
        and party != ''
123
        order by entered_house
124 20
        " . $direction, [":house" => $house,
125 20
            ":person_id" => $person_id])->first();
126
        if ($row) {
127 20
            $party = $row["party"];
128
            if ($party == 'Labour/Co-operative') {
129 20
                $party = 'Labour';
130 20
            } elseif ($party == 'Sinn Féin') {
131
                $party = 'Sinn Fein';
132 20
            }
133
            return slugify($party);
134
        } else {
135 20
            return null;
136
        }
137
138
    }
139
140 20
    /*
141 20
     * Determine if the member is a new member of a house where new is
142 20
     * within the last 6 months.
143 20
     *
144 20
     * @param int house - identifier for the house, defaults to 1 for westminster
0 ignored issues
show
Bug introduced by
The type MySociety\TheyWorkForYou\house was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
145
     *
146
     * @return boolean
147 20
     */
148
149
150
    public function isNew($house = HOUSE_TYPE_COMMONS) {
151
        $date_entered = $this->getEntryDate($house);
152
153
        if ($date_entered) {
154
            $date_entered = new \DateTime($date_entered);
155
            $now = new \DateTime();
156
157
            $diff = $date_entered->diff($now);
158
            if ($diff->y == 0 && $diff->m <= 6) {
159
                return true;
160
            }
161
        }
162
163
        return false;
164 1
    }
165 1
166
    /*
167 1
     * Get the date the person first entered a house. May not be for their current seat.
168 1
     *
169 1
     * @param int house - identifier for the house, defaults to 1 for westminster
170
     *
171 1
     * @return string - blank if no entry date for that house otherwise in YYYY-MM-DD format
172 1
     */
173 1
174
    public function getEntryDate($house = HOUSE_TYPE_COMMONS) {
175
        $date_entered = '';
176
177 1
        $entered_house = $this->entered_house($house);
178
179
        if ($entered_house) {
180
            $date_entered = $entered_house['date'];
181
        }
182
183
        return $date_entered;
184
    }
185
186
    /*
187
     * Get the date the person last left the house.
188 2
     *
189 2
     * @param int house - identifier for the house, defaults to 1 for westminster
190
     *
191 2
     * @return string - 9999-12-31 if they are still in that house otherwise in YYYY-MM-DD format
192
     */
193 2
194 2
    public function getLeftDate($house = HOUSE_TYPE_COMMONS) {
195
        $date_left = '';
196
197 2
        $left_house = $this->left_house($house);
198
199
        if ($left_house) {
200
            $date_left = $left_house['date'];
201
        }
202
203
        return $date_left;
204
    }
205
206
207
    public function getEUStance() {
208
        if (array_key_exists('eu_ref_stance', $this->extra_info())) {
209
            return $this->extra_info()['eu_ref_stance'];
210
        }
211
212
        return false;
213
    }
214
215
    /**
216
    * Image
217
    *
218
    * Return a URL for the member's image.
219
    *
220
    * @return string The URL of the member's image.
221
    */
222
223
    public function image() {
224
225
        $is_lord = $this->house(HOUSE_TYPE_LORDS);
226
        if ($is_lord) {
227
            [$image, $size] = Utility\Member::findMemberImage($this->person_id(), false, 'lord');
228
        } else {
229
            [$image, $size] = Utility\Member::findMemberImage($this->person_id(), false, true);
230
        }
231
232
        // We can determine if the image exists or not by testing if size is set
233
        if ($size !== null) {
234
            $exists = true;
235
        } else {
236
            $exists = false;
237
        }
238
239
        return [
0 ignored issues
show
Bug Best Practice introduced by
The expression return array('url' => $i...e, 'exists' => $exists) returns the type array<string,boolean|null|string> which is incompatible with the documented return type string.
Loading history...
240
            'url' => $image,
241
            'size' => $size,
242
            'exists' => $exists,
243
        ];
244
245
    }
246
247
    public function getMostRecentMembership() {
248
        $departures = $this->left_house();
249
250
        usort(
251
            $departures,
0 ignored issues
show
Bug introduced by
It seems like $departures can also be of type null; however, parameter $array of usort() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

251
            /** @scrutinizer ignore-type */ $departures,
Loading history...
252
            function ($a, $b) {
253
                if ($a['date'] == $b['date']) {
254
                    return 0;
255
                } elseif ($a['date'] < $b['date']) {
256
                    return -1;
257
                } else {
258
                    return 1;
259
                }
260
            }
261
        );
262
263
        $latest_membership = array_slice($departures, -1)[0];
264
        $latest_membership['current'] = ($latest_membership['date'] == '9999-12-31');
265
        $latest_entrance = $this->entered_house($latest_membership['house']);
266
        $latest_membership['start_date'] = $latest_entrance['date'];
267
        $latest_membership['end_date'] = $latest_membership['date'];
268
        $latest_membership['rep_name'] = $this->getRepNameForHouse($latest_membership['house']);
269
270
        return $latest_membership;
271
    }
272
273
    /**
274
    * Offices
275
    *
276
    * Return an array of Office objects held (or previously held) by the member.
277
    *
278
    * @param string $include_only  Restrict the list to include only "previous" or "current" offices.
279
    * @param bool   $ignore_committees Ignore offices that appear to be committee memberships.
280
    *
281
    * @return array An array of Office objects.
282
    */
283
284
    public function offices($include_only = null, $ignore_committees = false) {
285
286
        $out = [];
287
288
        if (array_key_exists('office', $this->extra_info())) {
289
            $office = $this->extra_info();
290
            $office = $office['office'];
291
292
            foreach ($office as $row) {
293
                if ($officeObject = $this->getOfficeObject($include_only, $ignore_committees, $row)) {
294
                    $out[] = $officeObject;
295
                }
296
            }
297
        }
298
299
        return $out;
300
301
    }
302
303
    private function getOfficeObject($include_only, $ignore_committees, $row) {
304
        if (!$this->includeOffice($include_only, $row['to_date'])) {
305
            return null;
306
        }
307
        if ($ignore_committees && strpos($row['moffice_id'], 'Committee')) {
308
            return null;
309
        }
310
311
        $officeObject = new Office();
312
        $officeObject->title = prettify_office($row['position'], $row['dept']);
313
        $officeObject->from_date = $row['from_date'];
314
        $officeObject->to_date = $row['to_date'];
315
        $officeObject->source = $row['source'];
316
        return $officeObject;
317
    }
318
319
    private function includeOffice($include_only, $to_date) {
320
        $include_office = true;
321
322
        // If we should only include previous offices, and the to date is in the future, suppress this office.
323
        if ($include_only == 'previous' and $to_date == '9999-12-31') {
324
            $include_office = false;
325
        }
326
327
        // If we should only include previous offices, and the to date is in the past, suppress this office.
328
        if ($include_only == 'current' and $to_date != '9999-12-31') {
329
            $include_office = false;
330
        }
331
332
        return $include_office;
333
    }
334
335
    /**
336
    * Get Other Parties String
337
    *
338
    * Return a readable list of party changes for this member.
339
    *
340
    * @return string|null A readable list of the party changes for this member.
341
    */
342
343
    public function getOtherPartiesString() {
344
345
        if (!empty($this->other_parties) && $this->party != 'Speaker' && $this->party != 'Deputy Speaker') {
346
            $output = 'Party was ';
347
            $other_parties = [];
348
            foreach ($this->other_parties as $r) {
349
                $other_parties[] = $r['from'] . ' until ' . format_date($r['date'], SHORTDATEFORMAT);
350
            }
351
            $output .= join('; ', $other_parties);
352
            return $output;
353
        } else {
354
            return null;
355
        }
356
357
    }
358
359
    /**
360
    * Get Other Constituencies String
361
    *
362
    * Return a readable list of other constituencies for this member.
363
    *
364
    * @return string|null A readable list of the other constituencies for this member.
365
    */
366
367
    public function getOtherConstituenciesString() {
368
369
        if ($this->other_constituencies) {
370
            return 'Also represented ' . join('; ', array_keys($this->other_constituencies));
371
        } else {
372
            return null;
373
        }
374
375
    }
376
377
    /**
378
    * Get Entered/Left Strings
379
    *
380
    * Return an array of readable strings covering when people entered or left
381
    * various houses. Returns an array since it's possible for a member to have
382
    * done several of these things.
383
    *
384
    * @return array An array of strings of when this member entered or left houses.
385
    */
386
    public function getEnterLeaveStrings() {
387
        $output = [];
388
389
        $output[] = $this->entered_house_line(HOUSE_TYPE_LORDS, gettext('House of Lords'));
390
391
        if (isset($this->left_house[HOUSE_TYPE_COMMONS]) && isset($this->entered_house[HOUSE_TYPE_LORDS])) {
392
            $string = '<strong>';
393
            $string .= sprintf(gettext('Previously MP for %s until %s'), $this->left_house[HOUSE_TYPE_COMMONS]['constituency'], $this->left_house[HOUSE_TYPE_COMMONS]['date_pretty']);
394
            $string .= '</strong>';
395
            if ($this->left_house[HOUSE_TYPE_COMMONS]['reason']) {
396
                $string .= ' &mdash; ' . $this->left_house[HOUSE_TYPE_COMMONS]['reason'];
397
            }
398
            $output[] = $string;
399
        }
400 1
401 1
        $output[] = $this->left_house_line(HOUSE_TYPE_LORDS, gettext('House of Lords'));
402
403 1
        if (isset($this->extra_info['lordbio'])) {
404
            $output[] = '<strong>Positions held at time of appointment:</strong> ' . $this->extra_info['lordbio'] .
405 1
                ' <small>(from <a href="' .
406
                $this->extra_info['lordbio_from'] . '">Number 10 press release</a>)</small>';
407
        }
408
409
        $output[] = $this->entered_house_line(HOUSE_TYPE_COMMONS, gettext('House of Commons'));
410
411
        # If they became a Lord, we handled this above.
412
        if ($this->house(HOUSE_TYPE_COMMONS) && !$this->current_member(HOUSE_TYPE_COMMONS) && !$this->house(HOUSE_TYPE_LORDS)) {
413
            $output[] = $this->left_house_line(HOUSE_TYPE_COMMONS, gettext('House of Commons'));
414
        }
415 1
416
        $output[] = $this->entered_house_line(HOUSE_TYPE_NI, gettext('Assembly'));
417 1
        $output[] = $this->left_house_line(HOUSE_TYPE_NI, gettext('Assembly'));
418
        $output[] = $this->entered_house_line(HOUSE_TYPE_SCOTLAND, gettext('Scottish Parliament'));
419
        $output[] = $this->left_house_line(HOUSE_TYPE_SCOTLAND, gettext('Scottish Parliament'));
420
        $output[] = $this->entered_house_line(HOUSE_TYPE_WALES, gettext('Senedd'));
421
        $output[] = $this->left_house_line(HOUSE_TYPE_WALES, gettext('Senedd'));
422
        $output[] = $this->entered_house_line(HOUSE_TYPE_LONDON_ASSEMBLY, gettext('London Assembly'));
423 1
        $output[] = $this->left_house_line(HOUSE_TYPE_LONDON_ASSEMBLY, gettext('London Assembly'));
424
425
        $output = array_values(array_filter($output));
426 1
        return $output;
427
    }
428
429
    private function entered_house_line($house, $house_name) {
430 1
        if (isset($this->entered_house[$house]['date'])) {
431 1
            $string = "<strong>";
432 1
            if (strlen($this->entered_house[$house]['date_pretty']) == 4) {
433 1
                $string .= sprintf(gettext("Entered the %s in %s"), $house_name, $this->entered_house[$house]['date_pretty']);
434 1
            } else {
435 1
                $string .= sprintf(gettext("Entered the %s on %s"), $house_name, $this->entered_house[$house]['date_pretty']);
436 1
            }
437 1
            $string .= '</strong>';
438
            if ($this->entered_house[$house]['reason']) {
439 1
                $string .= ' &mdash; ' . $this->entered_house[$house]['reason'];
440 1
            }
441
            return $string;
442
        }
443 1
    }
444 1
445 1
    private function left_house_line($house, $house_name) {
446 1
        if ($this->house($house) && !$this->current_member($house)) {
447
            $string = "<strong>";
448
            if (strlen($this->left_house[$house]['date_pretty']) == 4) {
449 1
                $string .= sprintf(gettext("Left the %s in %s"), $house_name, $this->left_house[$house]['date_pretty']);
450
            } else {
451 1
                $string .= sprintf(gettext("Left the %s on %s"), $house_name, $this->left_house[$house]['date_pretty']);
452 1
            }
453 1
            $string .= '</strong>';
454
            if ($this->left_house[$house]['reason']) {
455 1
                $string .= ' &mdash; ' . $this->left_house[$house]['reason'];
456
            }
457 1
            return $string;
458
        }
459 1
    }
460 1
461 1
    public function getPartyPolicyDiffs($partyCohort, $policiesList, $positions) {
462 1
        $policy_diffs = [];
463
        $party_positions = $partyCohort->getAllPolicyPositions($policiesList);
464
465 1
        if (!$party_positions) {
466
            return $policy_diffs;
467 1
        }
468 1
469 1
        foreach ($positions->positionsById as $policy_id => $details) {
470
            if ($details['has_strong'] && $details['score'] != -1 && isset($party_positions[$policy_id])) {
471 1
                $mp_score = $details['score'];
472
                $party_position = $party_positions[$policy_id];
473 1
                $party_score = $party_position['score'];
474
                $year_min = substr($party_position['date_min'], 0, 4);
475
                $year_max = substr($party_position['date_max'], 0, 4);
476
                if ($year_min == $year_max) {
477
                    $party_voting_summary = sprintf("%d votes, in %d", $party_position['divisions'], $year_min);
478
                } else {
479
                    $party_voting_summary = sprintf("%d votes, between %d–%d", $party_position['divisions'], $year_min, $year_max);
480
                }
481
482
                $score_diff = $this->calculatePolicyDiffScore($mp_score, $party_score);
483
484
                $policy_diffs[$policy_id] = [
485
                    'policy_text' => $details['policy'],
486
                    'score_difference' => $score_diff,
487
                    'person_position' => $details['position'],
488
                    'summary' => $details['summary'],
489
                    'party_position' => $party_position['position'],
490
                    'party_voting_summary' => $party_voting_summary,
491
                ];
492
            }
493
        }
494
495
        uasort($policy_diffs, function ($a, $b) {
496
            return $b['score_difference'] - $a['score_difference'];
497
        });
498
499
        return $policy_diffs;
500
    }
501
502
    private function calculatePolicyDiffScore($mp_score, $party_score) {
503
        $score_diff = abs($mp_score - $party_score);
504
        // if they are on opposite sides of mixture of for and against
505
        if (
506
            ($mp_score < 0.4 && $party_score > 0.6) ||
507
            ($mp_score > 0.6 && $party_score < 0.4)
508
        ) {
509
            $score_diff += 2;
510
            // if on is mixture of for and against and one is for/against
511
        } elseif (
512
            ($mp_score > 0.4 && $mp_score < 0.6 && ($party_score > 0.6 || $party_score < 0.4)) ||
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($mp_score > 0.4 && $mp_... 0.6 || $mp_score < 0.4, Probably Intended Meaning: $mp_score > 0.4 && $mp_s...0.6 || $mp_score < 0.4)
Loading history...
513
            ($party_score > 0.4 && $party_score < 0.6 && ($mp_score > 0.6 || $mp_score < 0.4))
514
        ) {
515
            $score_diff += 1;
516
        }
517
518
        return $score_diff;
519
    }
520
521
    public static function getRegionalList($postcode, $house, $type) {
522
        $db = new \ParlDB();
523
524
        $mreg = [];
525
        $constituencies = \MySociety\TheyWorkForYou\Utility\Postcode::postcodeToConstituencies($postcode);
526
        if (isset($constituencies[$type])) {
527
            $cons_name = $constituencies[$type];
528
            $query_base = "SELECT member.person_id, title, lordofname, given_name, family_name, constituency, house
529
                FROM member, person_names
530
                WHERE
531
                member.person_id = person_names.person_id
532
                AND person_names.type = 'name'
533
                AND constituency = :cons_name
534
                AND house = :house
535
                AND left_house >= start_date
536
                AND left_house <= end_date";
537
            $q = $db->query(
538
                "$query_base AND left_reason = 'still_in_office'",
539 1
                [
540 1
                    ':house' => $house,
541
                    ':cons_name' => $cons_name,
542 1
                ]
543 1
            );
544 1
            if (!$q->rows() && ($dissolution = Dissolution::db())) {
545 1
                $q = $db->query(
546 1
                    "$query_base AND $dissolution[query]",
547
                    [
548
                        ':house' => $house,
549
                        ':cons_name' => $cons_name,
550
                    ] + $dissolution['params']
551
                );
552
            }
553
554
            foreach ($q as $row) {
555 1
                $name = member_full_name($house, $row['title'], $row['given_name'], $row['family_name'], $row['lordofname']);
556
                $mreg[] = [
557 1
                    'person_id' => $row['person_id'],
558 1
                    'name' => $name,
559
                    'house' => $row['house'],
560
                    'constituency' => gettext($row['constituency']),
561 1
                ];
562
            }
563
        }
564
565
        return $mreg;
566
    }
567
568
    public static function getRepNameForHouse($house) {
569
        switch ($house) {
570 1
            case HOUSE_TYPE_COMMONS:
571 1
                $name = 'MP';
572 1
                break;
573 1
            case HOUSE_TYPE_LORDS:
574 1
                $name = 'Peer';
575 1
                break;
576 1
            case HOUSE_TYPE_NI:
577
                $name = 'MLA';
578
                break;
579
            case HOUSE_TYPE_SCOTLAND:
580
                $name = 'MSP';
581 1
                break;
582
            case HOUSE_TYPE_WALES:
583
                $name = 'MS';
584
                break;
585
            case HOUSE_TYPE_LONDON_ASSEMBLY:
586
                $name = 'London Assembly Member';
587
                break;
588
            case HOUSE_TYPE_ROYAL:
589
                $name = 'Member of royalty';
590
                break;
591
            default:
592
                $name = '';
593
        }
594
        return $name;
595
    }
596
597
}
598