Passed
Push — developer ( e0199d...a3edbd )
by Mariusz
63:10 queued 29:14
created

AbstractListView   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 339
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 6
dl 0
loc 339
rs 9.0399
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getFromApi() 0 6 1
A getInstance() 0 8 2
A getModuleName() 0 4 1
A setRawData() 0 5 1
A setFields() 0 5 1
A setLimit() 0 5 1
A setOffset() 0 5 1
A setOrder() 0 6 1
A setConditions() 0 4 1
A setCvId() 0 5 1
A loadRecordsList() 0 5 1
B getApiHeaders() 0 24 7
A getRecordsListModel() 0 19 5
A getHeaders() 0 11 3
A getCustomViews() 0 12 4
A getDefaultCustomView() 0 12 4
A getCount() 0 4 1
A getPage() 0 7 4
A setPage() 0 5 1
A isMorePages() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * The file contains: Abstract class ListView.
4
 *
5
 * @package Model
6
 *
7
 * @copyright YetiForce Sp. z o.o.
8
 * @license YetiForce Public License 3.0 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author Arkadiusz Adach <[email protected]>
10
 * @author    Mariusz Krzaczkowski <[email protected]>
11
 * @author    Radosław Skrzypczak <[email protected]>
12
 */
13
14
namespace YF\Modules\Base\Model;
15
16
/**
17
 * Abstract class ListView.
18
 */
19
abstract class AbstractListView
20
{
21
	/** @var string Module name. */
22
	protected $moduleName;
23
24
	/** @var string[] Column fields */
25
	protected $fields = [];
26
27
	/** @var array Records list from api. */
28
	protected $recordsList = [];
29
30
	/** @var int Current page. */
31
	private $page = 1;
32
33
	/** @var int The number of items on the page. */
34
	protected $limit = 0;
35
36
	/** @var int Offset. */
37
	protected $offset = 0;
38
39
	/** @var string Sorting direction. */
40
	protected $order;
41
42
	/** @var string Sets the ORDER BY part of the query record list. */
43
	protected $orderField;
44
45
	/** @var array Conditions. */
46
	protected $conditions = [];
47
48
	/** @var bool Use raw data. */
49
	protected $rawData = false;
50
51
	/** @var string Action name */
52
	protected $actionName = 'RecordsList';
53
54
	/** @var array Custom views */
55
	protected $customViews;
56
57
	/** @var int Custom view ID */
58
	protected $cvId;
59
60
	/**
61
	 * Get instance.
62
	 *
63
	 * @param string $moduleName
64
	 * @param string $viewName
65
	 *
66
	 * @return self
0 ignored issues
show
Documentation introduced by
Should the return type not be \self?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
67
	 */
68
	public static function getInstance(string $moduleName, string $viewName = 'ListView'): self
69
	{
70
		$handlerModule = \App\Loader::getModuleClassName($moduleName, 'Model', $viewName);
71
		$self = new $handlerModule();
72
		$self->moduleName = $moduleName;
73
		$self->limit = \App\Config::$itemsPrePage ?: 15;
74
		return $self;
75
	}
76
77
	/**
78
	 * Function to get the Module Model.
79
	 *
80
	 * @return string
81
	 */
82
	public function getModuleName(): string
83
	{
84
		return $this->moduleName;
85
	}
86
87
	/**
88
	 * Function to set raw data.
89
	 *
90
	 * @param bool $rawData
91
	 *
92
	 * @return self
93
	 */
94
	public function setRawData(bool $rawData): self
95
	{
96
		$this->rawData = $rawData;
97
		return $this;
98
	}
99
100
	/**
101
	 * Set custom fields.
102
	 *
103
	 * @param array $fields
104
	 *
105
	 * @return self
106
	 */
107
	public function setFields(array $fields): self
108
	{
109
		$this->fields = $fields;
110
		return $this;
111
	}
112
113
	/**
114
	 * Set limit.
115
	 *
116
	 * @param int $limit
117
	 *
118
	 * @return self
119
	 */
120
	public function setLimit(int $limit): self
121
	{
122
		$this->limit = $limit;
123
		return $this;
124
	}
125
126
	/**
127
	 * Set offset.
128
	 *
129
	 * @param int $offset
130
	 *
131
	 * @return self
132
	 */
133
	public function setOffset(int $offset): self
134
	{
135
		$this->offset = $offset;
136
		return $this;
137
	}
138
139
	/**
140
	 * Set order.
141
	 *
142
	 * @param string $field
143
	 * @param string $direction
144
	 *
145
	 * @return self
146
	 */
147
	public function setOrder(string $field, string $direction): self
148
	{
149
		$this->orderField = $field;
150
		$this->order = $direction;
151
		return $this;
152
	}
153
154
	/**
155
	 * Set conditions.
156
	 *
157
	 * @param array $conditions
158
	 *
159
	 * @return void
160
	 */
161
	public function setConditions(array $conditions)
162
	{
163
		$this->conditions = $conditions;
164
	}
165
166
	/**
167
	 * Set custom view ID.
168
	 *
169
	 * @param int $cvId
170
	 *
171
	 * @return $this
172
	 */
173
	public function setCvId(int $cvId): self
174
	{
175
		$this->cvId = $cvId;
176
		return $this;
177
	}
178
179
	/**
180
	 * Load a list of records from the API.
181
	 *
182
	 * @return self
183
	 */
184
	public function loadRecordsList(): self
185
	{
186
		$this->recordsList = $this->getFromApi($this->getApiHeaders());
187
		return $this;
188
	}
189
190
	/**
191
	 * Gets headers for api.
192
	 *
193
	 * @return array
194
	 */
195
	public function getApiHeaders(): array
196
	{
197
		$headers = [
198
			'x-row-count' => 1,
199
			'x-row-limit' => $this->limit,
200
			'x-row-offset' => $this->offset,
201
		];
202
		if (!empty($this->fields)) {
203
			$headers['x-fields'] = \App\Json::encode($this->fields);
204
		}
205
		if (!empty($this->conditions)) {
206
			$headers['x-condition'] = \App\Json::encode($this->conditions);
207
		}
208
		if ($this->rawData) {
209
			$headers['x-raw-data'] = 1;
210
		}
211
		if (!empty($this->order) && $this->orderField) {
212
			$headers['x-order-by'] = \App\Json::encode([$this->orderField => $this->order]);
213
		}
214
		if ($cvId = $this->getDefaultCustomView()) {
215
			$headers['x-cv-id'] = $cvId;
216
		}
217
		return $headers;
218
	}
219
220
	/**
221
	 * Get data from api.
222
	 *
223
	 * @param array $headers
224
	 *
225
	 * @return array
226
	 */
227
	protected function getFromApi(array $headers): array
228
	{
229
		$api = \App\Api::getInstance();
230
		$api->setCustomHeaders($headers);
231
		return $api->call($this->getModuleName() . '/' . $this->actionName);
232
	}
233
234
	/**
235
	 * Get records list model.
236
	 *
237
	 * @return Record[]
238
	 */
239
	public function getRecordsListModel(): array
240
	{
241
		$recordsModel = [];
242
		if (!empty($this->recordsList['records'])) {
243
			foreach ($this->recordsList['records'] as $id => $value) {
244
				$recordModel = Record::getInstance($this->getModuleName());
245
				if (isset($value['recordLabel'])) {
246
					$recordModel->setName($value['recordLabel']);
247
					unset($value['recordLabel']);
248
				}
249
				if (isset($this->recordsList['rawData'][$id])) {
250
					$recordModel->setRawData($this->recordsList['rawData'][$id]);
251
				}
252
				$recordModel->setData($value)->setId($id)->setPrivileges($this->recordsList['permissions'][$id]);
253
				$recordsModel[$id] = $recordModel;
254
			}
255
		}
256
		return $recordsModel;
257
	}
258
259
	/**
260
	 * Get headers of list.
261
	 *
262
	 * @return array
263
	 */
264
	public function getHeaders(): array
265
	{
266
		if (empty($this->recordsList)) {
267
			$headers = ['x-only-column' => 1];
268
			if ($cvId = $this->getDefaultCustomView()) {
269
				$headers['x-cv-id'] = $cvId;
270
			}
271
			$this->recordsList = $this->getFromApi($headers);
272
		}
273
		return $this->recordsList['headers'] ?? [];
274
	}
275
276
	/**
277
	 * Gets custom views.
278
	 *
279
	 * @return array
280
	 */
281
	public function getCustomViews(): array
282
	{
283
		if (null === $this->customViews) {
284
			if (\App\Cache::has('CustomViews', $this->getModuleName())) {
285
				$this->customViews = \App\Cache::get('CustomViews', $this->getModuleName());
0 ignored issues
show
Documentation Bug introduced by
It seems like \App\Cache::get('CustomV...$this->getModuleName()) can also be of type string. However, the property $customViews is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
286
			} else {
287
				$this->customViews = \App\Api::getInstance()->call($this->getModuleName() . '/CustomView') ?: [];
288
				\App\Cache::save('CustomViews', $this->getModuleName(), $this->customViews, \App\Cache::LONG);
289
			}
290
		}
291
		return $this->customViews;
292
	}
293
294
	/**
295
	 * Get default custom view ID.
296
	 *
297
	 * @return int|null
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
298
	 */
299
	public function getDefaultCustomView(): ?int
300
	{
301
		if (!$this->cvId) {
302
			foreach ($this->getCustomViews() as $cvId => $cvData) {
303
				if ($cvData['isDefault']) {
304
					$this->cvId = $cvId;
0 ignored issues
show
Documentation Bug introduced by
It seems like $cvId can also be of type string. However, the property $cvId is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
305
					break;
306
				}
307
			}
308
		}
309
		return $this->cvId;
310
	}
311
312
	/**
313
	 * Get all rows count.
314
	 *
315
	 * @return int
316
	 */
317
	public function getCount(): int
318
	{
319
		return $this->recordsList['numberOfAllRecords'] ?? 0;
320
	}
321
322
	/**
323
	 * Get current page.
324
	 *
325
	 * @return int
326
	 */
327
	public function getPage(): int
328
	{
329
		if (!$this->page) {
330
			$this->page = floor($this->recordsList['numberOfRecords'] / ($this->recordsList['numberOfAllRecords'] ?: 1)) ?: 1;
0 ignored issues
show
Documentation Bug introduced by
It seems like floor($this->recordsList...llRecords'] ?: 1)) ?: 1 can also be of type double. However, the property $page is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
331
		}
332
		return $this->page;
333
	}
334
335
	/**
336
	 * Sets page number.
337
	 *
338
	 * @param int $page
339
	 *
340
	 * @return $this
341
	 */
342
	public function setPage(int $page)
343
	{
344
		$this->page = $page;
345
		return $this;
346
	}
347
348
	/**
349
	 * Is there more pages.
350
	 *
351
	 * @return bool
352
	 */
353
	public function isMorePages(): bool
354
	{
355
		return $this->recordsList['isMorePages'] ?? false;
356
	}
357
}
358