Formatter   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 2
dl 0
loc 153
ccs 57
cts 57
cp 1
rs 9.2
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 53 27
A format() 0 4 1
A registerFormattingFunction() 0 11 2
A formatCentury() 0 8 2
A formatSerialPre() 0 4 1
A formatSerialPost() 0 4 1
A formatDelimiter() 0 4 1
A formatCheckDigit() 0 4 1
A formatSex() 0 4 1
A formatAge() 0 4 1
A formatLegalForm() 0 4 1
A formatBirthCounty() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace byrokrat\id\Formatter;
6
7
use byrokrat\id\IdInterface;
8
use byrokrat\id\Exception\LogicException;
9
use byrokrat\id\Exception\DateNotSupportedException;
10
11
class Formatter implements FormatTokens
12
{
13
    /**
14
     * Map of tokens to formatting function names
15
     */
16
    private const TOKEN_MAP = [
17
        self::TOKEN_DATE_CENTURY => 'formatCentury',
18
        self::TOKEN_SERIAL_PRE => 'formatSerialPre',
19
        self::TOKEN_SERIAL_POST => 'formatSerialPost',
20
        self::TOKEN_DELIMITER => 'formatDelimiter',
21
        self::TOKEN_CHECK_DIGIT => 'formatCheckDigit',
22
        self::TOKEN_SEX => 'formatSex',
23
        self::TOKEN_AGE => 'formatAge',
24
        self::TOKEN_LEGAL_FORM => 'formatLegalForm',
25
        self::TOKEN_BIRTH_COUNTY => 'formatBirthCounty',
26
    ];
27
28
    /**
29
     * @var \Closure Formatting function, takes an Id object and returns a string
30
     */
31
    private $formatter;
32
33
    /**
34
     * Create formatter from format string
35
     */
36
    public function __construct(string $format = '')
37 6
    {
38
        // Register empty formatting function
39
        $this->formatter = function () {
40
            return '';
41 6
        };
42
43
        // Used to track escaping state
44
        $escape = '';
45 6
46
        foreach (str_split($format) as $token) {
47 6
            switch ($escape . $token) {
48 6
                case self::TOKEN_DATE_YEAR_FULL:
49 6
                case self::TOKEN_DATE_YEAR:
50 6
                case self::TOKEN_DATE_MONTH:
51 5
                case self::TOKEN_DATE_MONTH_SHORT:
52 5
                case self::TOKEN_DATE_MONTH_TEXT:
53 5
                case self::TOKEN_DATE_MONTH_TEXT_SHORT:
54 5
                case self::TOKEN_DATE_MONTH_DAYS:
55 5
                case self::TOKEN_DATE_WEEK:
56 5
                case self::TOKEN_DATE_DAY:
57 5
                case self::TOKEN_DATE_DAY_SHORT:
58 5
                case self::TOKEN_DATE_DAY_TEXT:
59 5
                case self::TOKEN_DATE_DAY_TEST_SHORT:
60 5
                case self::TOKEN_DATE_DAY_NUMERIC:
61 5
                case self::TOKEN_DATE_DAY_NUMERIC_ISO:
62 5
                case self::TOKEN_DATE_DAY_OF_YEAR:
63 5
                    $this->registerFormattingFunction(function (IdInterface $idObject) use ($token) {
64
                        return $idObject->getBirthDate()->format($token);
65 2
                    });
66 2
                    break;
67 2
                case self::TOKEN_DATE_CENTURY:
68 5
                case self::TOKEN_SERIAL_PRE:
69 5
                case self::TOKEN_SERIAL_POST:
70 5
                case self::TOKEN_DELIMITER:
71 5
                case self::TOKEN_CHECK_DIGIT:
72 5
                case self::TOKEN_SEX:
73 5
                case self::TOKEN_AGE:
74 5
                case self::TOKEN_LEGAL_FORM:
75 4
                case self::TOKEN_BIRTH_COUNTY:
76 3
                    $this->registerFormattingFunction([$this, self::TOKEN_MAP[$token]]);
77 3
                    break;
78 3
                case self::TOKEN_ESCAPE:
79 3
                    $escape = $token;
80 1
                    break;
81 1
                default:
82
                    $escape = '';
83 3
                    $this->registerFormattingFunction(function () use ($token) {
84
                        return $token;
85 3
                    });
86 6
            }
87
        }
88
    }
89 6
90
    /**
91
     * Format id using registered formatting functions
92
     */
93
    public function format(IdInterface $idObject): string
94
    {
95
        return ($this->formatter)($idObject);
96
    }
97
98
    /**
99 6
     * Function must take an Id object and return a string
100
     *
101 6
     * @param mixed $formatter
102 6
     */
103 6
    private function registerFormattingFunction($formatter): void
104
    {
105 6
        if (!is_callable($formatter)) {
106
            throw new LogicException('Formatting function must be callable');
107
        }
108
109
        $oldFormatter = $this->formatter;
110
        $this->formatter = function (IdInterface $idObject) use ($oldFormatter, $formatter) {
111
            return $oldFormatter($idObject) . $formatter($idObject);
112
        };
113 6
    }
114
115 6
    private function formatCentury(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
116 6
    {
117
        try {
118
            return $idObject->getCentury();
119
        } catch (DateNotSupportedException $e) {
120
            return '00';
121
        }
122
    }
123
124
    private function formatSerialPre(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
125 1
    {
126
        return $idObject->getSerialPreDelimiter();
127 1
    }
128
129
    private function formatSerialPost(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
130
    {
131
        return $idObject->getSerialPostDelimiter();
132
    }
133
134
    private function formatDelimiter(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
135
    {
136 1
        return $idObject->getDelimiter();
137
    }
138 1
139
    private function formatCheckDigit(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
140
    {
141
        return $idObject->getCheckDigit();
142
    }
143
144
    private function formatSex(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
145
    {
146
        return $idObject->getSex();
147 1
    }
148
149 1
    private function formatAge(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
150
    {
151
        return (string)$idObject->getAge();
152
    }
153
154
    private function formatLegalForm(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
155
    {
156
        return $idObject->getLegalForm();
157
    }
158 1
159
    private function formatBirthCounty(IdInterface $idObject): string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
160 1
    {
161
        return $idObject->getBirthCounty();
162
    }
163
}
164