MinifyVars::optimizeBlock()   B
last analyzed

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