Complex classes like DelimiterStack 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 DelimiterStack, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class DelimiterStack |
||
18 | { |
||
19 | /** |
||
20 | * @var Delimiter|null |
||
21 | */ |
||
22 | protected $top; |
||
23 | |||
24 | public function getTop() |
||
28 | |||
29 | 864 | public function push(Delimiter $newDelimiter) |
|
39 | |||
40 | /** |
||
41 | * @param Delimiter|null $stackBottom |
||
42 | * |
||
43 | * @return Delimiter|null |
||
44 | */ |
||
45 | 1656 | public function findEarliest(Delimiter $stackBottom = null) |
|
54 | |||
55 | /** |
||
56 | * @param Delimiter $delimiter |
||
57 | */ |
||
58 | 864 | public function removeDelimiter(Delimiter $delimiter) |
|
71 | |||
72 | /** |
||
73 | * @param Delimiter|null $stackBottom |
||
74 | */ |
||
75 | 1656 | public function removeAll(Delimiter $stackBottom = null) |
|
81 | |||
82 | /** |
||
83 | * @param string $character |
||
84 | */ |
||
85 | 279 | public function removeEarlierMatches($character) |
|
96 | |||
97 | /** |
||
98 | * @param string|string[] $characters |
||
99 | * |
||
100 | * @return Delimiter|null |
||
101 | */ |
||
102 | 405 | public function searchByCharacter($characters) |
|
118 | |||
119 | /** |
||
120 | * @param string|string[] $characters |
||
121 | * @param callable $callback |
||
122 | * @param Delimiter $stackBottom |
||
123 | */ |
||
124 | 1656 | public function iterateByCharacters($characters, $callback, Delimiter $stackBottom = null) |
|
125 | { |
||
126 | 1656 | if (!is_array($characters)) { |
|
127 | $characters = [$characters]; |
||
128 | } |
||
129 | |||
130 | 1656 | $openersBottom = array_fill_keys($characters, $stackBottom); |
|
131 | |||
132 | // Find first closer above stackBottom |
||
133 | 1656 | $closer = $this->findEarliest($stackBottom); |
|
134 | |||
135 | 1656 | while ($closer !== null) { |
|
136 | 807 | $closerChar = $closer->getChar(); |
|
137 | |||
138 | 807 | if (!$closer->canClose() || !in_array($closerChar, $characters)) { |
|
139 | 768 | $closer = $closer->getNext(); |
|
140 | 768 | continue; |
|
141 | } |
||
142 | |||
143 | 408 | $oddMatch = false; |
|
144 | 408 | $opener = $this->findMatchingOpener($closer, $openersBottom, $stackBottom, $oddMatch); |
|
145 | 408 | if ($opener) { |
|
146 | 336 | $closer = $callback($opener, $closer, $this); |
|
147 | 408 | } elseif ($oddMatch) { |
|
148 | 15 | $closer = $closer->getNext(); |
|
149 | 15 | } else { |
|
150 | 117 | $oldCloser = $closer; |
|
151 | 117 | $closer = $closer->getNext(); |
|
152 | // Set lower bound for future searches for openers: |
||
153 | 117 | $openersBottom[$closerChar] = $oldCloser->getPrevious(); |
|
154 | 117 | if (!$oldCloser->canOpen()) { |
|
155 | // We can remove a closer that can't be an opener, |
||
156 | // once we've seen there's no matching opener: |
||
157 | 87 | $this->removeDelimiter($oldCloser); |
|
158 | 87 | } |
|
159 | 117 | continue; |
|
160 | } |
||
161 | 336 | } |
|
162 | 1656 | } |
|
163 | |||
164 | /** |
||
165 | * @param Delimiter $closer |
||
166 | * @param array $openersBottom |
||
167 | * @param Delimiter|null $stackBottom |
||
168 | * @param bool $oddMatch |
||
169 | * |
||
170 | * @return Delimiter|null |
||
171 | */ |
||
172 | 408 | protected function findMatchingOpener(Delimiter $closer, $openersBottom, Delimiter $stackBottom = null, &$oddMatch = false) |
|
173 | { |
||
174 | 408 | $closerChar = $closer->getChar(); |
|
175 | 408 | $opener = $closer->getPrevious(); |
|
176 | |||
177 | 408 | while ($opener !== null && $opener !== $stackBottom && $opener !== $openersBottom[$closerChar]) { |
|
178 | 384 | $oddMatch = ($closer->canOpen() || $opener->canClose()) && ($opener->getOrigDelims() + $closer->getOrigDelims()) % 3 === 0; |
|
179 | 384 | if ($opener->getChar() === $closerChar && $opener->canOpen() && !$oddMatch) { |
|
180 | 336 | return $opener; |
|
181 | } |
||
182 | |||
183 | 81 | $opener = $opener->getPrevious(); |
|
184 | 81 | } |
|
185 | 132 | } |
|
186 | |||
187 | /** |
||
188 | * @param Delimiter $closer |
||
189 | * @param array $openersBottom |
||
190 | * @param Delimiter|null $stackBottom |
||
191 | * |
||
192 | * @return Delimiter|null |
||
193 | * |
||
194 | * @deprecated Use findMatchingOpener() instead. This method will be removed in the next major release. |
||
195 | */ |
||
196 | protected function findFirstMatchingOpener(Delimiter $closer, $openersBottom, Delimiter $stackBottom = null) |
||
209 | } |
||
210 |