Completed
Push — mysql_improvements ( 2023bf...2e95ce )
by Michael
02:48
created

StatsJsonView   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 302
Duplicated Lines 2.65 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 99.21%

Importance

Changes 21
Bugs 3 Features 0
Metric Value
wmc 35
c 21
b 3
f 0
lcom 1
cbo 2
dl 8
loc 302
ccs 126
cts 127
cp 0.9921
rs 9

6 Methods

Rating   Name   Duplication   Size   Complexity  
A isAuthorizedRaw() 0 4 1
A setSource() 0 4 1
B buildResponseData() 0 27 5
C render() 0 60 9
B processSingleSource() 0 30 4
D sanitizeData() 8 84 15

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Stats\Views\Stats;
4
5
use Joomla\View\BaseJsonView;
6
7
/**
8
 * JSON response for requesting the stats data.
9
 *
10
 * @property-read  \Stats\Models\StatsModel  $model  The model object.
11
 *
12
 * @since          1.0
13
 */
14
class StatsJsonView extends BaseJsonView
15
{
16
	/**
17
	 * Flag if the response should return the raw data.
18
	 *
19
	 * @var    boolean
20
	 * @since  1.0
21
	 */
22
	private $authorizedRaw = false;
23
24
	/**
25
	 * Array holding the valid data sources.
26
	 *
27
	 * @var    array
28
	 * @since  1.0
29
	 */
30
	private $dataSources = ['php_version', 'db_type', 'db_version', 'cms_version', 'server_os'];
31
32
	/**
33
	 * The data source to return.
34
	 *
35
	 * @var    string
36
	 * @since  1.0
37
	 */
38
	private $source;
39
40
	/**
41
	 * Count of the number of items.
42
	 *
43
	 * @var    integer
44
	 * @since  1.0
45
	 */
46
	private $totalItems = 0;
47
48
	/**
49
	 * Set whether the raw data should be returned.
50
	 *
51
	 * @param   boolean  $authorizedRaw  Flag if the response should return the raw data.
52
	 *
53
	 * @return  void
54
	 *
55
	 * @since   1.0
56
	 */
57 1
	public function isAuthorizedRaw($authorizedRaw)
58
	{
59 1
		$this->authorizedRaw = $authorizedRaw;
60 1
	}
61
62
	/**
63
	 * Method to render the view.
64
	 *
65
	 * @return  string  The rendered view.
66
	 *
67
	 * @since   1.0
68
	 * @throws  \InvalidArgumentException
69
	 */
70 4
	public function render()
71
	{
72 4
		$items = $this->model->getItems($this->source);
73
74
		// Null out the model now to free some memory
75 4
		$this->model = null;
76
77 4
		if ($this->source)
78 4
		{
79 2
			return $this->processSingleSource($items);
0 ignored issues
show
Bug introduced by
It seems like $items defined by $this->model->getItems($this->source) on line 72 can also be of type null; however, Stats\Views\Stats\StatsJ...::processSingleSource() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
80
		}
81
82 2
		$php_version = [];
83 2
		$db_type     = [];
84 2
		$db_version  = [];
85 2
		$cms_version = [];
86 2
		$server_os   = [];
87
88
		// If we have the entire database, we have to loop within each group to put it all together
89 2
		foreach ($items as $group)
0 ignored issues
show
Bug introduced by
The expression $items of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
90
		{
91 2
			$this->totalItems = 0;
92 2
			foreach ($group as $item)
93
			{
94 2
				foreach ($this->dataSources as $source)
95
				{
96 2
					if (isset($item[$source]) && !is_null($item[$source]))
97 2
					{
98
						// Special case, if the server is empty then change the key to "unknown"
99 2
						if ($source === 'server_os' && empty(trim($item[$source])))
100 2
						{
101 2
							$item[$source] = 'unknown';
102 2
						}
103
104 2
						${$source}[$item[$source]] = $item['count'];
105
106 2
						$this->totalItems += $item['count'];
107 2
					}
108 2
				}
109 2
			}
110 2
		}
111
112 2
		unset($items);
113
114
		$data = [
115 2
			'php_version' => $php_version,
116 2
			'db_type'     => $db_type,
117 2
			'db_version'  => $db_version,
118 2
			'cms_version' => $cms_version,
119 2
			'server_os'   => $server_os,
120 2
		];
121
122 2
		$responseData = $this->buildResponseData($data);
123
124 2
		$responseData['total'] = $this->totalItems;
125
126 2
		$this->addData('data', $responseData);
127
128 2
		return parent::render();
129
	}
130
131
	/**
132
	 * Set the data source.
133
	 *
134
	 * @param   string  $source  Data source to return.
135
	 *
136
	 * @return  void
137
	 *
138
	 * @since   1.0
139
	 */
140 1
	public function setSource($source)
141
	{
142 1
		$this->source = $source;
143 1
	}
144
145
	/**
146
	 * Process the raw data into the response data format.
147
	 *
148
	 * @param   array  $data  The raw data array.
149
	 *
150
	 * @return  array
151
	 *
152
	 * @since   1.0
153
	 */
154 4
	private function buildResponseData(array $data)
155
	{
156 4
		$responseData = [];
157
158 4
		foreach ($data as $key => $value)
159
		{
160 4
			foreach ($value as $name => $count)
161
			{
162
				if ($name)
163 4
				{
164 4
					$responseData[$key][] = [
165 4
						'name'  => $name,
166
						'count' => $count
167 4
					];
168 4
				}
169 4
			}
170 4
		}
171
172 4
		unset($data);
173
174 4
		if (!$this->authorizedRaw)
175 4
		{
176 3
			$responseData = $this->sanitizeData($responseData);
177 3
		}
178
179 4
		return $responseData;
180
	}
181
182
	/**
183
	 * Process the response for a single data source.
184
	 *
185
	 * @param   array  $items  The source items to process.
186
	 *
187
	 * @return  string  The rendered view.
188
	 *
189
	 * @since   1.0
190
	 */
191 2
	private function processSingleSource(array $items)
192
	{
193
		$data = [
194 2
			${$this->source} = [],
195 2
		];
196
197 2
		$this->totalItems = 0;
198
199 2
		foreach ($items as $item)
200
		{
201
			// Special case, if the server is empty then change the key to "unknown"
202 2
			if ($this->source === 'server_os' && empty(trim($item[$this->source])))
203 2
			{
204 1
				$item[$this->source] = 'unknown';
205 1
			}
206
207 2
			$data[$this->source][$item[$this->source]] = $item['count'];
208 2
			$this->totalItems += $item['count'];
209 2
		}
210
211 2
		unset($generator);
212
213 2
		$responseData = $this->buildResponseData($data);
214
215 2
		$responseData['total'] = $this->totalItems;
216
217 2
		$this->addData('data', $responseData);
218
219 2
		return parent::render();
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (render() instead of processSingleSource()). Are you sure this is correct? If so, you might want to change this to $this->render().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
220
	}
221
222
	/**
223
	 * Sanitize the response data into summarized groups.
224
	 *
225
	 * @param   array  $responseData  The response data to sanitize.
226
	 *
227
	 * @return  array
228
	 *
229
	 * @since   1.0
230
	 */
231 3
	private function sanitizeData(array $responseData)
232
	{
233 3
		foreach ($responseData as $key => $dataGroup)
234
		{
235
			switch ($key)
236
			{
237 3
				case 'php_version':
238 3
				case 'db_version':
239
					// We're going to group by minor version branch here and convert to a percentage
240 2
					$counts = [];
241
242 2
					foreach ($dataGroup as $row)
243
					{
244 2
						$exploded = explode('.', $row['name']);
245 2
						$version  = $exploded[0] . '.' . (isset($exploded[1]) ? $exploded[1] : '0');
246
247
						// If the container does not exist, add it
248 2
						if (!isset($counts[$version]))
249 2
						{
250 2
							$counts[$version] = 0;
251 2
						}
252
253 2
						$counts[$version] += $row['count'];
254 2
					}
255
256 2
					$sanitizedData = [];
257
258 2 View Code Duplication
					foreach ($counts as $version => $count)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
259
					{
260 2
						$sanitizedData[$version] = round(($count / $this->totalItems) * 100, 2);
261 2
					}
262
263 2
					$responseData[$key] = $sanitizedData;
264
265 2
					break;
266
267 2
				case 'server_os':
268
					// We're going to group by operating system here
269 2
					$counts = [];
270
271 2
					foreach ($dataGroup as $row)
272
					{
273 2
						$fullOs = explode(' ', $row['name']);
274 2
						$os     = $fullOs[0];
275
276
						// If the container does not exist, add it
277 2
						if (!isset($counts[$os]))
278 2
						{
279 2
							$counts[$os] = 0;
280 2
						}
281
282 2
						$counts[$os] += $row['count'];
283 2
					}
284
285 2
					$sanitizedData = [];
286
287 2 View Code Duplication
					foreach ($counts as $os => $count)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
288
					{
289 2
						$sanitizedData[$os] = round(($count / $this->totalItems) * 100, 2);
290 2
					}
291
292 2
					$responseData[$key] = $sanitizedData;
293
294 2
					break;
295
296 1
				case 'db_type':
297 1
				case 'cms_version':
298 1
				default:
299
					// For now, group by the object name and figure out the percentages
300 1
					$sanitizedData = [];
301
302 1
					foreach ($dataGroup as $row)
303
					{
304 1
						$sanitizedData[$row['name']] = round(($row['count'] / $this->totalItems) * 100, 2);
305 1
					}
306
307 1
					$responseData[$key] = $sanitizedData;
308
309 1
					break;
310
			}
311 3
		}
312
313 3
		return $responseData;
314
	}
315
}
316