Issues (64)

src/Configurator/JavaScript/ConfigOptimizer.php (1 issue)

1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) The s9e authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Configurator\JavaScript;
9
10
/**
11
* This class creates local variables to deduplicate complex configValues
12
*/
13
class ConfigOptimizer
14
{
15
	/**
16
	* @var array Associative array of ConfigValue instances
17
	*/
18
	protected $configValues;
19
20
	/**
21
	* @var Encoder
22
	*/
23
	protected $encoder;
24
25
	/**
26
	* @var array Associative array with the length of the JavaScript representation of each value
27
	*/
28
	protected $jsLengths;
29
30
	/**
31
	* Constructor
32
	*
33
	* @param Encoder $encoder
34
	*/
35 8
	public function __construct(Encoder $encoder)
36
	{
37 8
		$this->encoder = $encoder;
38 8
		$this->reset();
39
	}
40
41
	/**
42
	* Return the var declarations for all deduplicated config values
43
	*
44
	* @return string JavaScript code
45
	*/
46 8
	public function getVarDeclarations()
47
	{
48 8
		asort($this->jsLengths);
49
50 8
		$src = '';
51 8
		foreach (array_keys($this->jsLengths) as $varName)
52
		{
53 8
			$configValue = $this->configValues[$varName];
54 8
			if ($configValue->isDeduplicated())
55
			{
56 4
				$src .= 'const ' . $varName . '=' . $this->encoder->encode($configValue->getValue()) . ";\n";
57
			}
58
		}
59
60 8
		return $src;
61
	}
62
63
	/**
64
	* Optimize given config object
65
	*
66
	* @param  array|Dictionary $object Original config object
67
	* @return array|Dictionary         Modified config object
68
	*/
69 8
	public function optimize($object)
70
	{
71 8
		return current($this->optimizeObjectContent([$object]))->getValue();
72
	}
73
74
	/**
75
	* Clear the deduplicated config values stored in this instance
76
	*
77
	* @return void
78
	*/
79 8
	public function reset()
80
	{
81 8
		$this->configValues = [];
82 8
		$this->jsLengths    = [];
83
	}
84
85
	/**
86
	* Test whether given value can be deduplicated
87
	*
88
	* @param  mixed $value
89
	* @return bool
90
	*/
91 8
	protected function canDeduplicate($value)
92
	{
93 8
		if (is_array($value) || $value instanceof Dictionary)
94
		{
95
			// Do not deduplicate empty arrays and dictionaries
96 8
			return (bool) count($value);
97
		}
98
99 6
		return ($value instanceof Code);
100
	}
101
102
	/**
103
	* Mark ConfigValue instances that have been used multiple times
104
	*
105
	* @return void
106
	*/
107 8
	protected function deduplicateConfigValues()
108
	{
109 8
		arsort($this->jsLengths);
110 8
		foreach (array_keys($this->jsLengths) as $varName)
111
		{
112 8
			$configValue = $this->configValues[$varName];
113 8
			if ($configValue->getUseCount() > 1)
114
			{
115 4
				$configValue->deduplicate();
116
			}
117
		}
118
	}
119
120
	/**
121
	* Return the name of the variable that will a given value
122
	*
123
	* @param  string $js JavaScript representation of the value
124
	* @return string
125
	*/
126 8
	protected function getVarName($js)
127
	{
128 8
		return sprintf('o%08X', crc32($js));
129
	}
130
131
	/**
132
	* Test whether given value is iterable
133
	*
134
	* @param  mixed $value
135
	* @return bool
136
	*/
137 8
	protected function isIterable($value)
138
	{
139 8
		return (is_array($value) || $value instanceof Dictionary);
140
	}
141
142
	/**
143
	* Optimize given object's content
144
	*
145
	* @param  array|Dictionary $object Original object
146
	* @return array|Dictionary         Modified object
147
	*/
148 8
	protected function optimizeObjectContent($object)
149
	{
150 8
		$object = $this->recordObject($object);
151 8
		$this->deduplicateConfigValues();
152
153 8
		return $object->getValue();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $object->getValue() also could return the type s9e\TextFormatter\Configurator\JavaScript\Code which is incompatible with the documented return type array|s9e\TextFormatter\...r\JavaScript\Dictionary.
Loading history...
154
	}
155
156
	/**
157
	* Record a given config object as a ConfigValue instance
158
	*
159
	* @param  array|Code|Dictionary $object Original object
160
	* @return ConfigValue                   Stored ConfigValue instance
161
	*/
162 8
	protected function recordObject($object)
163
	{
164 8
		$js      = $this->encoder->encode($object);
165 8
		$varName = $this->getVarName($js);
166
167 8
		if ($this->isIterable($object))
168
		{
169 8
			$object = $this->recordObjectContent($object);
170
		}
171
172 8
		if (!isset($this->configValues[$varName]))
173
		{
174 8
			$this->configValues[$varName] = new ConfigValue($object, $varName);
175 8
			$this->jsLengths[$varName]    = strlen($js);
176
		}
177 8
		$this->configValues[$varName]->incrementUseCount();
178
179 8
		return $this->configValues[$varName];
180
	}
181
182
	/**
183
	* Record the content of given config object
184
	*
185
	* @param  array|Dictionary $object Original object
186
	* @return array|Dictionary         Modified object containing ConfigValue instances
187
	*/
188 8
	protected function recordObjectContent($object)
189
	{
190 8
		foreach ($object as $k => $v)
191
		{
192 8
			if ($this->canDeduplicate($v) && !$this->shouldPreserve($v))
193
			{
194 8
				$object[$k] = $this->recordObject($v);
195
			}
196
		}
197
198 8
		return $object;
199
	}
200
201
	/**
202
	* Test whether given value should be preserved and not deduplicated
203
	*
204
	* @param  mixed $value
205
	* @return bool
206
	*/
207 8
	protected function shouldPreserve($value)
208
	{
209
		// Simple variables should be kept as-is
210 8
		return ($value instanceof Code && preg_match('(^\\w+$)', $value));
211
	}
212
}