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 |