Completed
Push — master ( 059804...113f38 )
by recca
16:20 queued 15:00
created

Rule   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 98.04%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 48
c 4
b 0
f 0
lcom 2
cbo 4
dl 0
loc 252
ccs 100
cts 102
cp 0.9804
rs 8.4864

10 Methods

Rating   Name   Duplication   Size   Complexity  
A zip3() 0 4 1
A zip5() 0 4 1
A zip() 0 4 1
A tokens() 0 4 1
A equalsToken() 0 16 4
A normalize() 0 18 2
B tokenize() 0 24 3
A __construct() 0 15 2
C match() 0 50 30
A normalizeAddress() 0 14 3

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Recca0120\Twzipcode;
4
5
use Closure;
6
use Recca0120\Lodash\JArray;
7
8
class Rule
9
{
10
    /**
11
     * $zip3.
12
     *
13
     * @var string
14
     */
15
    public $zip3;
16
17
    /**
18
     * $zip5.
19
     *
20
     * @var string
21
     */
22
    public $zip5;
23
24
    /**
25
     * $tokens.
26
     *
27
     * @var \Recca0120\Twzipcode\Address
28
     */
29
    public $address;
30
31
    /**
32
     * $tokens.
33
     *
34
     * @var \Recca0120\Lodash\JArray
35
     */
36
    public $tokens;
37
38
    /**
39
     * __construct.
40
     *
41
     * @param string $rule
42
     */
43 62
    public function __construct($rule)
44
    {
45 62
        if (preg_match('/^(\d+),?(.*)/', $rule, $m)) {
46 51
            $this->zip5 = $m[1];
47 51
            $this->zip3 = substr($this->zip5, 0, 3);
48 51
            $rule = $m[2];
49 51
        }
50
51 62
        $this->tokens = $this->tokenize(
52 62
            $rule,
53
            function ($address) {
54 62
                $this->address = new Address($address);
55 62
            }
56 62
        );
57 62
    }
58
59
    /**
60
     * zip3.
61
     *
62
     * @return string
63
     */
64 1
    public function zip3()
65
    {
66 1
        return $this->zip3;
67
    }
68
69
    /**
70
     * zip5.
71
     *
72
     * @return string
73
     */
74 23
    public function zip5()
75
    {
76 23
        return $this->zip5;
77
    }
78
79
    /**
80
     * zip.
81
     *
82
     * @return string
83
     */
84
    public function zip()
85
    {
86
        return $this->zip3();
87
    }
88
89
    /**
90
     * tokens.
91
     *
92
     * @return \Recca0120\Lodash\JArray
93
     */
94 18
    public function tokens()
95
    {
96 18
        return $this->tokens;
97
    }
98
99
    /**
100
     * match.
101
     *
102
     * @param Address|string $address
103
     * @return bool
104
     */
105 38
    public function match($address)
106
    {
107 38
        $ruleAddressTokens = $this->address->tokens();
108 38
        $address = $this->normalizeAddress(
109 38
            is_a($address, Address::class) === true ? $address : new Address($address),
0 ignored issues
show
Bug introduced by
It seems like is_a($address, \Recca012...pcode\Address($address) can also be of type string; however, Recca0120\Twzipcode\Rule::normalizeAddress() does only seem to accept object<Recca0120\Twzipcode\Address>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
110
            $ruleAddressTokens
111 38
        );
112 38
        $addressTokens = $address->tokens();
113
114 38
        $cur = $ruleAddressTokens->length() - 1;
115 38
        $cur -= $this->tokens->length() > 0 && $this->tokens->includes('全') === false;
116 38
        $cur -= $this->tokens->includes('至');
117
118 38
        if ($this->equalsToken($ruleAddressTokens, $addressTokens, $cur) === false) {
119 12
            return false;
120
        }
121
122 37
        $addressPoint = $address->getPoint($cur + 1);
123
124 37
        if ($this->tokens->length() > 0 && $addressPoint->isEmpty() === true) {
125 3
            return false;
126
        }
127
128 37
        $left = $this->address->getPoint($ruleAddressTokens->length() - 1);
129 37
        $right = $this->address->getPoint($ruleAddressTokens->length() - 2);
130
131 37
        foreach ($this->tokens as $token) {
132
            if (
133 33
                ($token === '單' && (bool) (($addressPoint->x & 1) === 1) === false) ||
134 33
                ($token === '雙' && (bool) (($addressPoint->x & 1) === 0) === false) ||
135 33
                ($token === '以上' && $addressPoint->compare($left, '>=') === false) ||
0 ignored issues
show
Documentation introduced by
$left is of type object<Recca0120\Twzipcode\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
136 33
                ($token === '以下' && $addressPoint->compare($left, '<=') === false) ||
0 ignored issues
show
Documentation introduced by
$left is of type object<Recca0120\Twzipcode\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
137 33
                ($token === '至' && (
138 9
                    $right->compare($addressPoint, '<=') && $addressPoint->compare($left, '<=') ||
0 ignored issues
show
Documentation introduced by
$addressPoint is of type object<Recca0120\Twzipcode\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$left is of type object<Recca0120\Twzipcode\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
139 8
                    $this->tokens->includes('含附號全') === true && ($addressPoint->x == $left->x)
140 33
                ) === false) ||
141 33
                ($token == '含附號' && ($addressPoint->x === $left->x) === false) ||
142 33
                ($token == '附號全' && ($addressPoint->x === $left->x && $addressPoint->y > 0) === false) ||
143 33
                ($token == '及以上附號' && $addressPoint->compare($left, '>=') === false) ||
0 ignored issues
show
Documentation introduced by
$left is of type object<Recca0120\Twzipcode\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
144 33
                ($token == '含附號以下' && (
145 4
                    $addressPoint->compare($left, '<=') ||
0 ignored issues
show
Documentation introduced by
$left is of type object<Recca0120\Twzipcode\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
146 4
                    $addressPoint->x === $left->x
147 4
                ) === false)
148 33
            ) {
149 25
                return false;
150
            }
151 37
        }
152
153 37
        return true;
154
    }
155
156
    /**
157
     * normalizeAddress.
158
     *
159
     * @param Address $ruleAddressTokens
160
     * @param \Recca0120\Lodash\JArray $ruleAddressTokens
161
     * @return array
162
     */
163 38
    protected function normalizeAddress(Address $address, JArray $ruleAddressTokens)
164
    {
165
        $removeUnits = array_diff(['里', '鄰', '巷', '弄'], (array) $ruleAddressTokens->map(function ($token) {
166 38
            return isset($token[Address::UNIT]) === true ? $token[Address::UNIT] : '';
167 38
        })->values());
168
169 38
        return new Address(
170
            new JArray($address->tokens()->filter(function ($token) use ($removeUnits) {
171 38
                return isset($token[Address::UNIT]) === true && in_array($token[Address::UNIT], $removeUnits, true) === false;
172
            })->map(function ($token) {
173 38
                return implode('', $token);
174 38
            }))
175 38
        );
176
    }
177
178
    /**
179
     * equalsToken.
180
     *
181
     * @param \Recca0120\Lodash\JArray $ruleAddressTokens
182
     * @param \Recca0120\Lodash\JArray $addressTokens
183
     * @param int $cur
184
     * @return bool
185
     */
186 38
    protected function equalsToken($ruleAddressTokens, $addressTokens, $cur)
187
    {
188 38
        if ($cur >= $addressTokens->length()) {
189 4
            return false;
190
        }
191
192 38
        $i = $cur;
193 38
        while ($i >= 0) {
194 38
            if ($ruleAddressTokens[$i] !== $addressTokens[$i]) {
195 11
                return false;
196
            }
197 37
            $i -= 1;
198 37
        }
199
200 37
        return true;
201
    }
202
203
    /**
204
     * normalize.
205
     *
206
     * @param string $rule
207
     * @return \Recca0120\Twzipcode\Normalizer
208
     */
209 62
    protected function normalize($rule)
210
    {
211 62
        $pattern = '((?P<no>\d+)之)?\s*(?P<left>\d+)至之?\s*(?P<right>\d+)(?P<unit>\w)';
212
213
        return (new Normalizer($rule))->normalize()->replace('/'.$pattern.'/u', function ($m) {
214 5
            $prefix = ':left:unit至:right:unit';
215 5
            if (empty($m['no']) === false) {
216 3
                $prefix = ':no之:left:unit至:no之:right:unit';
217 3
            }
218
219 5
            return strtr($prefix, [
220 5
                ':no' => $m['no'],
221 5
                ':left' => $m['left'],
222 5
                ':right' => $m['right'],
223 5
                ':unit' => $m['unit'],
224 5
            ]);
225 62
        });
226
    }
227
228
    /**
229
     * tokenize.
230
     *
231
     * @param string $rule
232
     * @param \Closure $addressResolver
233
     * @return \Recca0120\Lodash\JArray
234
     */
235 62
    protected function tokenize($rule, Closure $addressResolver)
236
    {
237 62
        $tokens = new JArray();
238
239
        $pattern = [
240 62
            '及以上附號|含附號以下|含附號全|含附號',
241 62
            '以下|以上',
242 62
            '附號全',
243 62
            '[連至單雙全](?=[\d全]|$)',
244 62
        ];
245
246 62
        $addressResolver($this->normalize($rule)->replace('/'.implode('|', $pattern).'/u', function ($m) use ($tokens) {
247 60
            $token = &$m[0];
248 60
            if ($token === '連') {
249 18
                return;
250
            }
251
252 60
            $tokens->append($token);
253
254 60
            return $token === '附號全' ? '號' : '';
255 62
        }));
256
257 62
        return $tokens;
258
    }
259
}
260