VariableService   B
last analyzed

Complexity

Total Complexity 47

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 116
c 3
b 0
f 0
dl 0
loc 234
rs 8.64
wmc 47

8 Methods

Rating   Name   Duplication   Size   Complexity  
B parseFilter() 0 61 9
C replaceTextVariables() 0 32 12
A parseFormat() 0 6 2
A replaceThresholdsVariables() 0 11 5
A __construct() 0 8 1
A replaceDatasourceColumns() 0 5 2
B replaceFilterVariables() 0 28 8
B replaceTextVariablesSingle() 0 20 8

How to fix   Complexity   

Complex Class

Complex classes like VariableService 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.

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 VariableService, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Analytics
4
 *
5
 * SPDX-FileCopyrightText: 2019-2022 Marcel Scherello
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8
9
namespace OCA\Analytics\Service;
10
11
use OCA\Analytics\Db\DatasetMapper;
12
use OCP\IDateTimeFormatter;
0 ignored issues
show
Bug introduced by
The type OCP\IDateTimeFormatter was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use Psr\Log\LoggerInterface;
0 ignored issues
show
Bug introduced by
The type Psr\Log\LoggerInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
15
class VariableService {
16
	private $logger;
17
	private $DatasetMapper;
18
	private $IDateTimeFormatter;
19
20
	public function __construct(
21
		LoggerInterface    $logger,
22
		DatasetMapper      $DatasetMapper,
23
		IDateTimeFormatter $IDateTimeFormatter
24
	) {
25
		$this->logger = $logger;
26
		$this->DatasetMapper = $DatasetMapper;
27
		$this->IDateTimeFormatter = $IDateTimeFormatter;
28
	}
29
30
	/**
31
	 * replace %*% text variables in thresholds
32
	 *
33
	 * @param array $thresholds
34
	 * @return array
35
	 */
36
	public function replaceThresholdsVariables($thresholds) {
37
		foreach ($thresholds as &$threshold) {
38
			$fields = ['dimension1', 'dimension2'];
39
			foreach ($fields as $field) {
40
				isset($threshold[$field]) ? $name = $threshold[$field] : $name = '';
41
				$parsed = $this->parseFilter($name);
42
				if (!$parsed) break;
0 ignored issues
show
Bug Best Practice introduced by
The expression $parsed of type array<string,integer|mixed|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
43
				$threshold[$field] = $parsed['6$startDate'];
44
			}
45
		}
46
		return $thresholds;
47
	}
48
49
	/**
50
	 * replace %*% text variables in name and subheader
51
	 *
52
	 * @param array $datasetMetadata
53
	 * @return array
54
	 */
55
	public function replaceTextVariables($datasetMetadata) {
56
		$fields = ['name', 'subheader'];
57
		foreach ($fields as $field) {
58
			isset($datasetMetadata[$field]) ? $name = $datasetMetadata[$field] : $name = '';
59
60
			preg_match_all("/%.*?%/", $name, $matches);
61
			if (count($matches[0]) > 0) {
62
				foreach ($matches[0] as $match) {
63
					$replace = null;
64
					if ($match === '%currentDate%') {
65
						$replace = $this->IDateTimeFormatter->formatDate(time(), 'short');
66
					} elseif ($match === '%currentTime%') {
67
						$replace = $this->IDateTimeFormatter->formatTime(time(), 'short');
68
					} elseif ($match === '%now%') {
69
						$replace = time();
70
					} elseif ($match === '%lastUpdateDate%') {
71
						$timestamp = $this->DatasetMapper->getLastUpdate($datasetMetadata['dataset']);
72
						$replace = $this->IDateTimeFormatter->formatDate($timestamp, 'short');
73
					} elseif ($match === '%lastUpdateTime%') {
74
						$timestamp = $this->DatasetMapper->getLastUpdate($datasetMetadata['dataset']);
75
						$replace = $this->IDateTimeFormatter->formatTime($timestamp, 'short');
76
					} elseif ($match === '%owner%') {
77
						$owner = $this->DatasetMapper->getOwner($datasetMetadata['dataset']);
78
						$replace = $owner;
79
					}
80
					if ($replace !== null) {
81
						$datasetMetadata[$field] = preg_replace('/' . $match . '/', $replace, $datasetMetadata[$field]);
82
					}
83
				}
84
			}
85
		}
86
		return $datasetMetadata;
87
	}
88
89
	/**
90
	 * replace variables in single field
91
	 * used in: API
92
	 *
93
	 * @param $field
94
	 * @return array
95
	 */
96
	public function replaceTextVariablesSingle($field) {
97
		if ($field !== null) {
98
			preg_match_all("/%.*?%/", $field, $matches);
99
			if (count($matches[0]) > 0) {
100
				foreach ($matches[0] as $match) {
101
					$replace = null;
102
					if ($match === '%currentDate%') {
103
						$replace = $this->IDateTimeFormatter->formatDate(time(), 'short');
104
					} elseif ($match === '%currentTime%') {
105
						$replace = $this->IDateTimeFormatter->formatTime(time(), 'short');
106
					} elseif ($match === '%now%') {
107
						$replace = time();
108
					}
109
					if ($replace !== null) {
110
						$field = preg_replace('/' . $match . '/', $replace, $field);
111
					}
112
				}
113
			}
114
		}
115
		return $field;
116
	}
117
118
	/**
119
	 * replace variables in single field
120
	 * used in: API
121
	 *
122
	 * @param $columns
123
	 * @return string
124
	 */
125
	public function replaceDatasourceColumns($columns) {
126
		$parsed = $this->parseFilter($columns);
127
		$format = $this->parseFormat($columns);
128
		if (!$parsed) return $columns;
0 ignored issues
show
introduced by
$parsed is a non-empty array, thus ! $parsed is always false.
Loading history...
Bug Best Practice introduced by
The expression $parsed of type array<string,integer|mixed|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
129
		return date($format, $parsed['value']);
130
	}
131
132
	/**
133
	 * replace variables in filters and apply format
134
	 *
135
	 * @param $reportMetadata
136
	 * @return array
137
	 */
138
	public function replaceFilterVariables($reportMetadata) {
139
		if ($reportMetadata['filteroptions'] !== null) {
140
			$filteroptions = json_decode($reportMetadata['filteroptions'], true);
141
			if (isset($filteroptions['filter'])) {
142
				foreach ($filteroptions['filter'] as $key => $value) {
143
					$parsed = $this->parseFilter($value['value']);
144
145
					if (!$parsed) continue;
0 ignored issues
show
Bug Best Practice introduced by
The expression $parsed of type array<string,integer|mixed|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
146
147
					// if a parser is selected in the chart options, it should also be valid here automatically
148
					if (isset($reportMetadata['chartoptions'])) {
149
						$chartOptions = json_decode($reportMetadata['chartoptions'], true);
150
						if (isset($chartOptions['scales']['xAxes']['time']['parser'])) {
151
							$format = $chartOptions['scales']['xAxes']['time']['parser'];
0 ignored issues
show
Unused Code introduced by
The assignment to $format is dead and can be removed.
Loading history...
152
						}
153
					}
154
					$format = $this->parseFormat($value['value']);
155
156
					// translate commonly known X timestamp format to U for php
157
					if ($format === 'X') $format = 'U';
158
159
					$filteroptions['filter'][$key]['value'] = date($format, $parsed['value']);
160
					//$filteroptions['filter'][$key]['option'] = $parsed['option'];
161
				}
162
			}
163
			$reportMetadata['filteroptions'] = json_encode($filteroptions);
164
		}
165
		return $reportMetadata;
166
	}
167
168
	/**
169
	 * parsing of %*% variables
170
	 *
171
	 * @param $filter
172
	 * @return array|bool
173
	 */
174
	private function parseFilter($filter) {
175
		preg_match_all("/(?<=%).*(?=%)/", $filter, $matches);
176
		if (count($matches[0]) > 0) {
177
			$filter = $matches[0][0];
178
			preg_match('/(last|next|current|to|yester)?/', $filter, $directionMatch); // direction
179
			preg_match('/[0-9]+/', $filter, $offsetMatch); // how much
180
			preg_match('/(day|days|week|weeks|month|months|year|years)$/', $filter, $unitMatch); // unit
181
182
			if (!$directionMatch[0] || !$unitMatch[0]) {
183
				// no known text variables found
184
				return false;
185
			}
186
187
			// if no offset is specified, apply 1 as default
188
			!isset($offsetMatch[0]) ? $offset = 1 : $offset = $offsetMatch[0];
189
190
			// remove "s" to unify e.g. weeks => week
191
			$unit = rtrim($unitMatch[0], 's');
192
193
			if ($directionMatch[0] === "last" || $directionMatch[0] === "yester") {
194
				// go back
195
				$direction = '-';
196
			} elseif ($directionMatch[0] === "next") {
197
				// go forward
198
				$direction = '+';
199
			} else {
200
				// current
201
				$direction = '+';
202
				$offset = 0;
203
			}
204
205
			// create a usable string for php like "+3 days"
206
			$timeString = $direction . $offset . ' ' . $unit;
207
			// get a timestamp of the target date
208
			$baseDate = strtotime($timeString);
209
210
			// get the correct format depending of the unit. e.g. first day of the month in case unit is "month"
211
			if ($unit === 'day') {
212
				$startString = 'today';
213
			} else {
214
				$startString = 'first day of this ' . $unit;
215
			}
216
			$startTS = strtotime($startString, $baseDate);
217
			$start = date("Y-m-d", $startTS);
218
219
			$return = [
220
				'value' => $startTS,
221
				'option' => 'GT',
222
				'1$filter' => $filter,
223
				'2$timestring' => $timeString,
224
				'3$target' => $baseDate,
225
				'4$target_clean' => date("Y-m-d", $baseDate),
226
				'5$startString' => $startString,
227
				'6$startDate' => $start,
228
				'7$startTS' => $startTS,
229
			];
230
			//$this->logger->debug('parseFilter: '. json_encode($return));
231
		} else {
232
			$return = false;
233
		}
234
		return $return;
235
	}
236
237
	/**
238
	 * parsing of ( ) format instructions
239
	 *
240
	 * @param $filter
241
	 * @return string
242
	 */
243
	private function parseFormat($filter) {
244
		preg_match_all("/(?<=\().*(?=\))/", $filter, $matches);
245
		if (count($matches[0]) > 0) {
246
			return $matches[0][0];
247
		} else {
248
			return 'Y-m-d H:m:s';
249
		}
250
	}
251
}