Completed
Push — master ( fc1df1...d515dd )
by Josh
01:55
created

MinifyVars::optimizeBlock()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 11
cts 11
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 4
nop 2
crap 5
1
<?php
2
3
/**
4
* @package   s9e\SourceOptimizer
5
* @copyright Copyright (c) 2014-2017 The s9e Authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\SourceOptimizer\Passes;
9
10
use s9e\SourceOptimizer\ContextHelper;
11
use s9e\SourceOptimizer\Pass;
12
use s9e\SourceOptimizer\TokenStream;
13
14
class MinifyVars extends Pass
15
{
16
	/**
17
	* @var integer Number of variables processed in current function block
18
	*/
19
	protected $cnt;
20
21
	/**
22
	* @var string Regexp that matches variable names that need to be preserved
23
	*/
24
	public $preserveRegexp = '(^\\$(?:\\$|__|(?:this|GLOBALS|_[A-Z]+|php_errormsg|HTTP_RAW_POST_DATA|http_response_header|arg[cv]))$)S';
25
26
	/**
27
	* @var TokenStream Token stream of the source being processed
28
	*/
29
	protected $stream;
30
31
	/**
32
	* @var array Map of [original name => minified name]
33
	*/
34
	protected $varNames;
35
36
	/**
37
	* {@inheritdoc}
38
	*/
39 8
	public function optimize(TokenStream $stream)
40
	{
41 8
		$this->stream = $stream;
42 8
		ContextHelper::forEachFunction(
43 8
			$this->stream,
44 8
			function ($startOffset, $endOffset)
45
			{
46 8
				$this->optimizeBlock($startOffset, $endOffset);
47 8
			}
48
		);
49 8
	}
50
51
	/**
52
	* Test whether current token is a variable that can be minified
53
	*
54
	* @return bool
55
	*/
56 8
	protected function canBeMinified()
57
	{
58 8
		if (!$this->stream->is(T_VARIABLE))
59
		{
60 8
			return false;
61
		}
62
63 7
		return !preg_match($this->preserveRegexp, $this->stream->currentText());
64
	}
65
66
	/**
67
	* Generate a minified name for given variable
68
	*
69
	* @param  string $varName Original variable
70
	* @return string          Minified variable
71
	*/
72 6
	protected function getName($varName)
73
	{
74 6
		if (!isset($this->varNames[$varName]))
75
		{
76 6
			$this->varNames[$varName] = $this->generateName();
77
		}
78
79 6
		return $this->varNames[$varName];
80
	}
81
82
	/**
83
	* Generate a minified variable name
84
	*
85
	* @return string
86
	*/
87 6
	protected function generateName()
88
	{
89 6
		$n = $this->cnt;
90 6
		$chars = '_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
91
92
		// Increment the counter and skip over the digits range if the name would start with one
93 6
		$this->cnt += ($this->cnt % 63 < 52) ? 1 : 11;
94
95 6
		$varName = '$';
96
		do
97
		{
98 6
			$varName .= $chars[$n % 63];
99 6
			$n = floor($n / 63);
100
		}
101 6
		while ($n > 0);
102
103 6
		return $varName;
104
	}
105
106
	/**
107
	* Handle the current double colon token
108
	*
109
	* @return void
110
	*/
111 2
	protected function handleDoubleColon()
112
	{
113
		// Save the offset of the double colon then go to the next significant token, e.g. foo::$bar
114 2
		$offset = $this->stream->key();
115 2
		$this->stream->next();
116 2
		$this->stream->skipNoise();
117 2
		if (!$this->stream->is(T_VARIABLE))
118
		{
119
			return;
120
		}
121
122
		// Test whether the variable is followed by a parenthesis. If so, that makes it a dynamic
123
		// method call and we should minify the variable
124 2
		$this->stream->next();
125 2
		$this->stream->skipNoise();
126 2
		if ($this->stream->current() === '(')
127
		{
128
			// Rewind to the double colon
129 1
			$this->stream->seek($offset);
130
		}
131 2
	}
132
133
	/**
134
	* Minify variables in given function block
135
	*
136
	* @param  integer $startOffset
137
	* @param  integer $endOffset
138
	* @return void
139
	*/
140 8
	protected function optimizeBlock($startOffset, $endOffset)
141
	{
142 8
		$this->resetNames();
143 8
		$this->stream->seek($startOffset);
144 8
		while ($this->stream->valid() && $this->stream->key() <= $endOffset)
145
		{
146 8
			if ($this->stream->is(T_DOUBLE_COLON))
147
			{
148 2
				$this->handleDoubleColon();
149
			}
150 8
			elseif ($this->canBeMinified())
151
			{
152 6
				$varName = $this->stream->currentText();
153 6
				$this->stream->replace([T_VARIABLE, $this->getName($varName)]);
154
			}
155 8
			$this->stream->next();
156
		}
157 8
	}
158
159
	/**
160
	* Reset the map of variable names
161
	*
162
	* @return void
163
	*/
164 8
	protected function resetNames()
165
	{
166 8
		$this->cnt      = 0;
167 8
		$this->varNames = [];
168
	}
169
}