Completed
Push — master ( 10f95b...bf15d4 )
by Michael
09:02
created

StatsJsonView   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 345
Duplicated Lines 4.64 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 5.5%

Importance

Changes 0
Metric Value
wmc 44
lcom 1
cbo 1
dl 16
loc 345
ccs 6
cts 109
cp 0.055
rs 8.3396
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A isAuthorizedRaw() 0 4 1
C render() 4 71 11
A setSource() 0 4 1
B buildResponseData() 0 27 5
C processSingleSource() 4 50 10
D sanitizeData() 8 84 15

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like StatsJsonView 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 StatsJsonView, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Joomla! Statistics Server
4
 *
5
 * @copyright  Copyright (C) 2013 - 2017 Open Source Matters, Inc. All rights reserved.
6
 * @license    http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License Version 2 or Later
7
 */
8
9
namespace Joomla\StatsServer\Views\Stats;
10
11
use Joomla\StatsServer\Models\StatsModel;
12
use Joomla\View\BaseJsonView;
13
14
/**
15
 * JSON response for requesting the stats data.
16
 *
17
 * @since  1.0
18
 */
19
class StatsJsonView extends BaseJsonView
20
{
21
	/**
22
	 * Flag if the response should return the raw data.
23
	 *
24
	 * @var    boolean
25
	 * @since  1.0
26
	 */
27
	private $authorizedRaw = false;
28
29
	/**
30
	 * Array holding the valid data sources.
31
	 *
32
	 * @var    array
33
	 * @since  1.0
34
	 */
35
	private $dataSources = ['php_version', 'db_type', 'db_version', 'cms_version', 'server_os'];
36
37
	/**
38
	 * The data source to return.
39
	 *
40
	 * @var    string
41
	 * @since  1.0
42
	 */
43
	private $source = '';
44
45
	/**
46
	 * Count of the number of items.
47
	 *
48
	 * @var    integer
49
	 * @since  1.0
50
	 */
51
	private $totalItems = 0;
52
53
	/**
54
	 * Instantiate the view.
55
	 *
56
	 * @param   StatsModel  $model  The model object.
57
	 *
58
	 * @since   1.0
59
	 */
60
	public function __construct(StatsModel $model)
61
	{
62
		$this->model = $model;
0 ignored issues
show
Bug introduced by
The property model does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
63
	}
64
65
	/**
66
	 * Set whether the raw data should be returned.
67
	 *
68
	 * @param   bool  $authorizedRaw  Flag if the response should return the raw data.
69
	 *
70
	 * @return  void
71
	 *
72
	 * @since   1.0
73
	 */
74 1
	public function isAuthorizedRaw(bool $authorizedRaw)
75
	{
76 1
		$this->authorizedRaw = $authorizedRaw;
77 1
	}
78
79
	/**
80
	 * Method to render the view.
81
	 *
82
	 * @return  string  The rendered view.
83
	 *
84
	 * @since   1.0
85
	 * @throws  \InvalidArgumentException
86
	 */
87
	public function render()
88
	{
89
		$items = $this->model->getItems($this->source);
90
91
		// Null out the model now to free some memory
92
		$this->model = null;
93
94
		if ($this->source)
95
		{
96
			return $this->processSingleSource($items);
97
		}
98
99
		$php_version = [];
100
		$db_type     = [];
101
		$db_version  = [];
102
		$cms_version = [];
103
		$server_os   = [];
104
105
		// If we have the entire database, we have to loop within each group to put it all together
106
		foreach ($items as $group)
107
		{
108
			$this->totalItems += count($group);
109
110
			foreach ($group as $item)
111
			{
112
				foreach ($this->dataSources as $source)
113
				{
114
					if (isset($item[$source]) && !is_null($item[$source]))
115
					{
116
						// Special case, if the server is empty then change the key to "unknown"
117
						if ($source === 'server_os' && empty($item[$source]))
118
						{
119
							if (!isset(${$source}['unknown']))
120
							{
121
								${$source}['unknown'] = 0;
122
							}
123
124
							${$source}['unknown']++;
125
						}
126
						else
127
						{
128 View Code Duplication
							if (!isset(${$source}[$item[$source]]))
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...
129
							{
130
								${$source}[$item[$source]] = 0;
131
							}
132
133
							${$source}[$item[$source]]++;
134
						}
135
					}
136
				}
137
			}
138
		}
139
140
		unset($items);
141
142
		$data = [
143
			'php_version' => $php_version,
144
			'db_type'     => $db_type,
145
			'db_version'  => $db_version,
146
			'cms_version' => $cms_version,
147
			'server_os'   => $server_os,
148
		];
149
150
		$responseData = $this->buildResponseData($data);
151
152
		$responseData['total'] = $this->totalItems;
153
154
		$this->addData('data', $responseData);
155
156
		return parent::render();
157
	}
158
159
	/**
160
	 * Set the data source.
161
	 *
162
	 * @param   string  $source  Data source to return.
163
	 *
164
	 * @return  void
165
	 *
166
	 * @since   1.0
167
	 */
168 1
	public function setSource(string $source)
169
	{
170 1
		$this->source = $source;
171 1
	}
172
173
	/**
174
	 * Process the raw data into the response data format.
175
	 *
176
	 * @param   array  $data  The raw data array.
177
	 *
178
	 * @return  array
179
	 *
180
	 * @since   1.0
181
	 */
182
	private function buildResponseData(array $data) : array
183
	{
184
		$responseData = [];
185
186
		foreach ($data as $key => $value)
187
		{
188
			foreach ($value as $name => $count)
189
			{
190
				if ($name)
191
				{
192
					$responseData[$key][] = [
193
						'name'  => $name,
194
						'count' => $count
195
					];
196
				}
197
			}
198
		}
199
200
		unset($data);
201
202
		if (!$this->authorizedRaw)
203
		{
204
			$responseData = $this->sanitizeData($responseData);
205
		}
206
207
		return $responseData;
208
	}
209
210
	/**
211
	 * Process the response for a single data source.
212
	 *
213
	 * @param   \Generator  $generator  The source items to process.
214
	 *
215
	 * @return  string  The rendered view.
216
	 *
217
	 * @since   1.0
218
	 */
219
	private function processSingleSource(\Generator $generator) : string
220
	{
221
		$data = [
222
			${$this->source} = [],
223
		];
224
225
		foreach ($generator as $group)
226
		{
227
			$this->totalItems += count($group);
228
229
			foreach ($group as $item)
230
			{
231
				foreach ($this->dataSources as $source)
232
				{
233
					if (isset($item[$source]) && !is_null($item[$source]))
234
					{
235
						// Special case, if the server is empty then change the key to "unknown"
236
						if ($source === 'server_os' && empty($item[$source]))
237
						{
238
							if (!isset($data[$source]['unknown']))
239
							{
240
								$data[$source]['unknown'] = 0;
241
							}
242
243
							$data[$source]['unknown']++;
244
						}
245
						else
246
						{
247 View Code Duplication
							if (!isset($data[$source][$item[$source]]))
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...
248
							{
249
								$data[$source][$item[$source]] = 0;
250
							}
251
252
							$data[$source][$item[$source]]++;
253
						}
254
					}
255
				}
256
			}
257
		}
258
259
		unset($generator);
260
261
		$responseData = $this->buildResponseData($data);
262
263
		$responseData['total'] = $this->totalItems;
264
265
		$this->addData('data', $responseData);
266
267
		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...
268
	}
269
270
	/**
271
	 * Sanitize the response data into summarized groups.
272
	 *
273
	 * @param   array  $responseData  The response data to sanitize.
274
	 *
275
	 * @return  array
276
	 *
277
	 * @since   1.0
278
	 */
279
	private function sanitizeData(array $responseData) : array
280
	{
281
		foreach ($responseData as $key => $dataGroup)
282
		{
283
			switch ($key)
284
			{
285
				case 'php_version':
286
				case 'db_version':
287
					// We're going to group by minor version branch here and convert to a percentage
288
					$counts = [];
289
290
					foreach ($dataGroup as $row)
291
					{
292
						$exploded = explode('.', $row['name']);
293
						$version  = $exploded[0] . '.' . (isset($exploded[1]) ? $exploded[1] : '0');
294
295
						// If the container does not exist, add it
296
						if (!isset($counts[$version]))
297
						{
298
							$counts[$version] = 0;
299
						}
300
301
						$counts[$version] += $row['count'];
302
					}
303
304
					$sanitizedData = [];
305
306 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...
307
					{
308
						$sanitizedData[$version] = round(($count / $this->totalItems) * 100, 2);
309
					}
310
311
					$responseData[$key] = $sanitizedData;
312
313
					break;
314
315
				case 'server_os':
316
					// We're going to group by operating system here
317
					$counts = [];
318
319
					foreach ($dataGroup as $row)
320
					{
321
						$fullOs = explode(' ', $row['name']);
322
						$os     = $fullOs[0];
323
324
						// If the container does not exist, add it
325
						if (!isset($counts[$os]))
326
						{
327
							$counts[$os] = 0;
328
						}
329
330
						$counts[$os] += $row['count'];
331
					}
332
333
					$sanitizedData = [];
334
335 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...
336
					{
337
						$sanitizedData[$os] = round(($count / $this->totalItems) * 100, 2);
338
					}
339
340
					$responseData[$key] = $sanitizedData;
341
342
					break;
343
344
				case 'db_type':
345
				case 'cms_version':
346
				default:
347
					// For now, group by the object name and figure out the percentages
348
					$sanitizedData = [];
349
350
					foreach ($dataGroup as $row)
351
					{
352
						$sanitizedData[$row['name']] = round(($row['count'] / $this->totalItems) * 100, 2);
353
					}
354
355
					$responseData[$key] = $sanitizedData;
356
357
					break;
358
			}
359
		}
360
361
		return $responseData;
362
	}
363
}
364