Complex classes like JSMin 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 JSMin, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 48 | class JSMin {
|
||
| 49 | const ORD_LF = 10; |
||
| 50 | const ORD_SPACE = 32; |
||
| 51 | |||
| 52 | protected $a = ''; |
||
| 53 | protected $b = ''; |
||
| 54 | protected $input = ''; |
||
| 55 | protected $inputIndex = 0; |
||
| 56 | protected $inputLength = 0; |
||
| 57 | protected $lookAhead = null; |
||
| 58 | protected $output = ''; |
||
| 59 | |||
| 60 | // -- Public Static Methods -------------------------------------------------- |
||
| 61 | |||
| 62 | public static function minify($js) {
|
||
| 66 | |||
| 67 | // -- Public Instance Methods ------------------------------------------------ |
||
| 68 | |||
| 69 | public function __construct($input) {
|
||
| 73 | |||
| 74 | // -- Protected Instance Methods --------------------------------------------- |
||
| 75 | |||
| 76 | |||
| 77 | |||
| 78 | /* action -- do something! What you do is determined by the argument: |
||
| 79 | 1 Output A. Copy B to A. Get the next B. |
||
| 80 | 2 Copy B to A. Get the next B. (Delete A). |
||
| 81 | 3 Get the next B. (Delete B). |
||
| 82 | action treats a string as a single character. Wow! |
||
| 83 | action recognizes a regular expression if it is preceded by ( or , or =. |
||
| 84 | */ |
||
| 85 | protected function action($d) {
|
||
| 86 | switch($d) {
|
||
| 87 | case 1: |
||
| 88 | $this->output .= $this->a; |
||
| 89 | |||
| 90 | case 2: |
||
| 91 | $this->a = $this->b; |
||
| 92 | |||
| 93 | if ($this->a === "'" || $this->a === '"') {
|
||
| 94 | for (;;) {
|
||
| 95 | $this->output .= $this->a; |
||
| 96 | $this->a = $this->get(); |
||
| 97 | |||
| 98 | if ($this->a === $this->b) {
|
||
| 99 | break; |
||
| 100 | } |
||
| 101 | |||
| 102 | if (ord($this->a) <= self::ORD_LF) {
|
||
| 103 | throw new JSMinException('Unterminated string literal.');
|
||
| 104 | } |
||
| 105 | |||
| 106 | if ($this->a === '\\') {
|
||
| 107 | $this->output .= $this->a; |
||
| 108 | $this->a = $this->get(); |
||
| 109 | } |
||
| 110 | } |
||
| 111 | } |
||
| 112 | |||
| 113 | case 3: |
||
| 114 | $this->b = $this->next(); |
||
| 115 | |||
| 116 | if ($this->b === '/' && ( |
||
| 117 | $this->a === '(' || $this->a === ',' || $this->a === '=' ||
|
||
| 118 | $this->a === ':' || $this->a === '[' || $this->a === '!' || |
||
| 119 | $this->a === '&' || $this->a === '|' || $this->a === '?' || |
||
| 120 | $this->a === '{' || $this->a === '}' || $this->a === ';' ||
|
||
| 121 | $this->a === "\n" )) {
|
||
| 122 | |||
| 123 | $this->output .= $this->a . $this->b; |
||
| 124 | |||
| 125 | for (;;) {
|
||
| 126 | $this->a = $this->get(); |
||
| 127 | |||
| 128 | if ($this->a === '[') {
|
||
| 129 | /* |
||
| 130 | inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460: |
||
| 131 | return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
|
||
| 132 | */ |
||
| 133 | for (;;) {
|
||
| 134 | $this->output .= $this->a; |
||
| 135 | $this->a = $this->get(); |
||
| 136 | |||
| 137 | if ($this->a === ']') {
|
||
| 138 | break; |
||
| 139 | } elseif ($this->a === '\\') {
|
||
| 140 | $this->output .= $this->a; |
||
| 141 | $this->a = $this->get(); |
||
| 142 | } elseif (ord($this->a) <= self::ORD_LF) {
|
||
| 143 | throw new JSMinException('Unterminated regular expression set in regex literal.');
|
||
| 144 | } |
||
| 145 | } |
||
| 146 | } elseif ($this->a === '/') {
|
||
| 147 | break; |
||
| 148 | } elseif ($this->a === '\\') {
|
||
| 149 | $this->output .= $this->a; |
||
| 150 | $this->a = $this->get(); |
||
| 151 | } elseif (ord($this->a) <= self::ORD_LF) {
|
||
| 152 | throw new JSMinException('Unterminated regular expression literal.');
|
||
| 153 | } |
||
| 154 | |||
| 155 | $this->output .= $this->a; |
||
| 156 | } |
||
| 157 | |||
| 158 | $this->b = $this->next(); |
||
| 159 | } |
||
| 160 | } |
||
| 161 | } |
||
| 162 | |||
| 163 | protected function get() {
|
||
| 186 | |||
| 187 | /* isAlphanum -- return true if the character is a letter, digit, underscore, |
||
| 188 | dollar sign, or non-ASCII character. |
||
| 189 | */ |
||
| 190 | protected function isAlphaNum($c) {
|
||
| 193 | |||
| 194 | protected function min() {
|
||
| 274 | |||
| 275 | /* next -- get the next character, excluding comments. peek() is used to see |
||
| 276 | if a '/' is followed by a '/' or '*'. |
||
| 277 | */ |
||
| 278 | protected function next() {
|
||
| 279 | $c = $this->get(); |
||
| 280 | |||
| 281 | if ($c === '/') {
|
||
| 282 | switch($this->peek()) {
|
||
| 283 | case '/': |
||
| 284 | for (;;) {
|
||
| 285 | $c = $this->get(); |
||
| 286 | |||
| 287 | if (ord($c) <= self::ORD_LF) {
|
||
| 288 | return $c; |
||
| 289 | } |
||
| 290 | } |
||
| 291 | |||
| 292 | case '*': |
||
| 293 | $this->get(); |
||
| 294 | |||
| 295 | for (;;) {
|
||
| 296 | switch($this->get()) {
|
||
| 297 | case '*': |
||
| 298 | if ($this->peek() === '/') {
|
||
| 299 | $this->get(); |
||
| 300 | return ' '; |
||
| 301 | } |
||
| 302 | break; |
||
| 303 | |||
| 304 | case null: |
||
| 305 | throw new JSMinException('Unterminated comment.');
|
||
| 306 | } |
||
| 307 | } |
||
| 308 | |||
| 309 | default: |
||
| 310 | return $c; |
||
| 311 | } |
||
| 312 | } |
||
| 313 | |||
| 314 | return $c; |
||
| 315 | } |
||
| 316 | |||
| 317 | protected function peek() {
|
||
| 321 | } |
||
| 322 | |||
| 323 | // -- Exceptions --------------------------------------------------------------- |
||
| 326 |
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.
A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.