Traits   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 387
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 66
eloc 105
c 2
b 0
f 1
dl 0
loc 387
rs 3.12

13 Methods

Rating   Name   Duplication   Size   Complexity  
A deleteListItem() 0 12 3
A __clone() 0 23 6
A deleteListItems() 0 11 3
B getListItem() 0 29 8
A getDomains() 0 3 1
A getListItemsDeleted() 0 7 2
A addListItem() 0 26 6
B getRefItems() 0 16 7
A getName() 0 10 4
A prepareListItems() 0 21 5
D getListItems() 0 42 18
A getLabel() 0 3 1
A initListItems() 0 7 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/**
4
 * @license LGPLv3, https://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2018-2024
6
 * @package MShop
7
 * @subpackage Common
8
 */
9
10
11
namespace Aimeos\MShop\Common\Item\ListsRef;
12
13
14
/**
15
 * Common trait for items containing list items
16
 *
17
 * @package MShop
18
 * @subpackage Common
19
 */
20
trait Traits
21
{
22
	private bool $listPrepared = false;
23
	private array $listItems = [];
24
	private array $listRefItems = [];
25
	private array $listRmItems = [];
26
	private array $listRmMap = [];
27
	private array $listMap = [];
28
	private int $listMax = 0;
29
30
31
	/**
32
	 * Creates a deep clone of all objects
33
	 */
34
	public function __clone()
35
	{
36
		parent::__clone();
37
38
		foreach( $this->listItems as $domain => $list )
39
		{
40
			foreach( $list as $id => $item ) {
41
				$this->listItems[$domain][$id] = clone $item;
42
			}
43
		}
44
45
		foreach( $this->listRefItems as $domain => $list )
46
		{
47
			foreach( $list as $id => $item ) {
48
				$this->listRefItems[$domain][$id] = clone $item;
49
			}
50
		}
51
52
		foreach( $this->listRmItems as $key => $item ) {
53
			$this->listRmItems[$key] = clone $item;
54
		}
55
56
		$this->listPrepared = false;
57
	}
58
59
60
	/**
61
	 * Adds a new or overwrite an existing list item which references the given domain item (created if it doesn't exist)
62
	 *
63
	 * @param string $domain Name of the domain (e.g. media, text, etc.)
64
	 * @param \Aimeos\MShop\Common\Item\Lists\Iface $listItem List item referencing the new domain item
65
	 * @param \Aimeos\MShop\Common\Item\Iface|null $refItem New item added to the given domain or null if no item should be referenced
66
	 * @return \Aimeos\MShop\Common\Item\ListsRef\Iface Self object for method chaining
67
	 */
68
	public function addListItem( string $domain, \Aimeos\MShop\Common\Item\Lists\Iface $listItem, \Aimeos\MShop\Common\Item\Iface $refItem = null ) : \Aimeos\MShop\Common\Item\ListsRef\Iface
69
	{
70
		if( $refItem !== null )
71
		{
72
			$id = $refItem->getId() ?: '#' . $this->listMax++;
73
			$listItem->setRefId( $id );
74
75
			if( $refItem instanceof \Aimeos\MShop\Common\Item\Domain\Iface ) {
76
				$refItem->setDomain( $this->getResourceType() );
77
			}
78
79
			$this->listRefItems[$domain][$id] = $refItem;
80
		}
81
82
		$id = $listItem->getId() ?: '_' . $this->getId() . '_' . $domain . '_' . $listItem->getType() . '_' . $listItem->getRefId();
83
84
		unset( $this->listItems[$domain][$id] ); // append at the end
85
		$this->listItems[$domain][$id] = $listItem->setDomain( $domain )->setRefItem( $refItem );
86
87
		if( isset( $this->listMap[$domain] ) )
88
		{
89
			unset( $this->listMap[$domain][$listItem->getType()][$listItem->getRefId()] ); // append at the end
90
			$this->listMap[$domain][$listItem->getType()][$listItem->getRefId()] = $listItem;
91
		}
92
93
		return $this;
94
	}
95
96
97
	/**
98
	 * Removes a list item which references the given domain item (removed as well if it exists)
99
	 *
100
	 * @param string $domain Name of the domain (e.g. media, text, etc.)
101
	 * @param \Aimeos\MShop\Common\Item\Lists\Iface $listItem List item referencing the domain item
102
	 * @param \Aimeos\MShop\Common\Item\Iface|null $refItem Existing item removed from the given domain or null if item shouldn't be removed
103
	 * @return \Aimeos\MShop\Common\Item\ListsRef\Iface Self object for method chaining
104
	 */
105
	public function deleteListItem( string $domain, \Aimeos\MShop\Common\Item\Lists\Iface $listItem, \Aimeos\MShop\Common\Item\Iface $refItem = null ) : \Aimeos\MShop\Common\Item\ListsRef\Iface
106
	{
107
		if( isset( $this->listItems[$domain] )
108
			&& ( $key = array_search( $listItem, $this->listItems[$domain], true ) ) !== false
109
		) {
110
			$this->listRmItems[] = $this->listRmMap[$domain][] = $listItem->setRefItem( $refItem );
111
112
			unset( $this->listMap[$domain][$listItem->getType()][$listItem->getRefId()] );
113
			unset( $this->listItems[$domain][$key] );
114
		}
115
116
		return $this;
117
	}
118
119
120
	/**
121
	 * Removes a list of list items which references their domain items (removed as well if it exists)
122
	 *
123
	 * @param \Aimeos\MShop\Common\Item\Lists\Iface[] $items Existing list items
124
	 * @param bool $all True to delete referenced items as well, false for list items only
125
	 * @return \Aimeos\MShop\Common\Item\ListsRef\Iface Self object for method chaining
126
	 * @throws \Aimeos\MShop\Exception If an item isn't a list item or isn't found
127
	 */
128
	public function deleteListItems( iterable $items, bool $all = false ) : \Aimeos\MShop\Common\Item\ListsRef\Iface
129
	{
130
		map( $items )->implements( \Aimeos\MShop\Common\Item\Lists\Iface::class, true );
131
132
		foreach( $items as $item )
133
		{
134
			$refItem = ( $all === true ? $item->getRefItem() : null );
135
			$this->deleteListItem( $item->getDomain(), $item, $refItem );
136
		}
137
138
		return $this;
139
	}
140
141
142
	/**
143
	 * Returns the domains for which items are available
144
	 *
145
	 * @return string[] List of domain names
146
	 */
147
	public function getDomains() : array
148
	{
149
		return array_keys( $this->listItems );
150
	}
151
152
153
	/**
154
	 * Returns the deleted list items which include the domain items if available
155
	 *
156
	 * @param string|null $domain Domain name to get the deleted list items for
157
	 * @return \Aimeos\Map Associative list of domains as keys list items as values or list items only
158
	 */
159
	public function getListItemsDeleted( string $domain = null ) : \Aimeos\Map
160
	{
161
		if( $domain !== null ) {
162
			return map( $this->listRmMap[$domain] ?? [] );
163
		}
164
165
		return map( $this->listRmItems );
166
	}
167
168
169
	/**
170
	 * Returns the list item for the given reference ID, domain and list type
171
	 *
172
	 * @param string $domain Name of the domain (e.g. product, text, etc.)
173
	 * @param string $listtype Name of the list item type
174
	 * @param string $refId Unique ID of the referenced item
175
	 * @param bool $active True to return only active items, false to return all
176
	 * @return \Aimeos\MShop\Common\Item\Lists\Iface|null Matching list item or null if none
177
	 */
178
	public function getListItem( string $domain, string $listtype, string $refId, bool $active = true ) : ?\Aimeos\MShop\Common\Item\Lists\Iface
179
	{
180
		if( !isset( $this->listMap[$domain] ) && isset( $this->listItems[$domain] ) )
181
		{
182
			$map = [];
183
184
			foreach( $this->listItems[$domain] as $listItem ) {
185
				$map[$listItem->getType()][$listItem->getRefId()] = $listItem;
186
			}
187
188
			$this->listMap[$domain] = $map;
189
		}
190
191
		if( isset( $this->listMap[$domain][$listtype][$refId] ) )
192
		{
193
			$listItem = $this->listMap[$domain][$listtype][$refId];
194
195
			if( $active === true && $listItem->isAvailable() === false ) {
196
				return null;
197
			}
198
199
			if( isset( $this->listRefItems[$domain][$refId] ) ) {
200
				$listItem->setRefItem( $this->listRefItems[$domain][$refId] );
201
			}
202
203
			return $listItem;
204
		}
205
206
		return null;
207
	}
208
209
210
	/**
211
	 * Returns the list items attached, optionally filtered by domain and list type.
212
	 *
213
	 * The reference parameter in search() must have been set accordingly
214
	 * to the requested domain to get the items. Otherwise, no items will be
215
	 * returned by this method.
216
	 *
217
	 * @param array|string|null $domain Name/Names of the domain (e.g. product, text, etc.) or null for all
218
	 * @param array|string|null $listtype Name/Names of the list item type or null for all
219
	 * @param array|string|null $type Name/Names of the item type or null for all
220
	 * @param bool $active True to return only active items, false to return all
221
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
222
	 */
223
	public function getListItems( $domain = null, $listtype = null, $type = null, bool $active = true ) : \Aimeos\Map
224
	{
225
		$result = [];
226
		$this->prepareListItems();
227
		$fcn = static::macro( 'listFilter' );
228
229
		$iface = \Aimeos\MShop\Common\Item\TypeRef\Iface::class;
230
		$listTypes = ( is_array( $listtype ) ? $listtype : array( $listtype ) );
231
		$types = ( is_array( $type ) ? $type : array( $type ) );
232
233
234
		foreach( $this->listItems as $dname => $list )
235
		{
236
			if( is_array( $domain ) && !in_array( $dname, $domain ) || is_string( $domain ) && $dname !== $domain ) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (is_array($domain) && ! ...) && $dname !== $domain, Probably Intended Meaning: is_array($domain) && (! ... && $dname !== $domain)
Loading history...
237
				continue;
238
			}
239
240
			$set = [];
241
242
			foreach( $list as $id => $item )
243
			{
244
				$refItem = $item->getRefItem();
245
246
				if( $type && ( !$refItem || !( $refItem instanceof $iface ) || !in_array( $refItem->getType(), $types ) ) ) {
247
					continue;
248
				}
249
250
				if( $listtype && !in_array( $item->getType(), $listTypes ) ) {
251
					continue;
252
				}
253
254
				if( $active && !$item->isAvailable() ) {
255
					continue;
256
				}
257
258
				$set[$id] = $item;
259
			}
260
261
			$result = array_replace( $result, $fcn ? $fcn( $set ) : $set );
262
		}
263
264
		return map( $result );
265
	}
266
267
268
	/**
269
	 * Returns the product, text, etc. items filtered by domain and optionally by type and list type.
270
	 *
271
	 * The reference parameter in search() must have been set accordingly
272
	 * to the requested domain to get the items. Otherwise, no items will be
273
	 * returned by this method.
274
	 *
275
	 * @param array|string|null $domain Name/Names of the domain (e.g. product, text, etc.) or null for all
276
	 * @param array|string|null $type Name/Names of the item type or null for all
277
	 * @param array|string|null $listtype Name/Names of the list item type or null for all
278
	 * @param bool $active True to return only active items, false to return all
279
	 * @return \Aimeos\Map List of items implementing \Aimeos\MShop\Common\Item\Iface
280
	 */
281
	public function getRefItems( $domain = null, $type = null, $listtype = null, bool $active = true ) : \Aimeos\Map
282
	{
283
		$list = [];
284
285
		foreach( $this->getListItems( $domain, $listtype, $type, $active ) as $listItem )
286
		{
287
			if( ( $refItem = $listItem->getRefItem() ) !== null && ( $active === false || $refItem->isAvailable() ) ) {
288
				$list[$listItem->getDomain()][$listItem->getRefId()] = $refItem;
289
			}
290
		}
291
292
		if( is_array( $domain ) || $domain === null ) {
293
			return map( $list );
294
		}
295
296
		return map( $list[$domain] ?? [] );
297
	}
298
299
300
	/**
301
	 * Returns the label of the item.
302
	 * This method should be implemented in the derived class if a label column is available.
303
	 *
304
	 * @return string Label of the item
305
	 */
306
	public function getLabel() : string
307
	{
308
		return '';
309
	}
310
311
312
	/**
313
	 * Returns the localized text type of the item or the internal label if no name is available.
314
	 *
315
	 * @param string $type Text type to be returned
316
	 * @param string|null $langId Two letter ISO Language code of the text
317
	 * @return string Specified text type or label of the item
318
	 */
319
	public function getName( string $type = 'name', string $langId = null ) : string
320
	{
321
		foreach( $this->getRefItems( 'text', $type ) as $textItem )
322
		{
323
			if( $textItem->getLanguageId() === $langId || $langId === null ) {
324
				return $textItem->getContent();
325
			}
326
		}
327
328
		return $this->getLabel();
329
	}
330
331
332
	/**
333
	 * Returns the unique ID of the item.
334
	 *
335
	 * @return string|null ID of the item
336
	 */
337
	abstract public function getId() : ?string;
338
339
340
	/**
341
	 * Sets the modified flag of the object.
342
	 *
343
	 * @return \Aimeos\MShop\Common\Item\Iface Item for chaining method calls
344
	 */
345
	abstract public function setModified() : \Aimeos\MShop\Common\Item\Iface;
346
347
348
	/**
349
	 * Returns the item type
350
	 *
351
	 * @return string Item type, subtypes are separated by slashes
352
	 */
353
	abstract public function getResourceType() : string;
354
355
356
	/**
357
	 * Registers a custom macro that has access to the class properties if called non-static
358
	 *
359
	 * @param string $name Macro name
360
	 * @param \Closure|null $function Anonymous function
361
	 * @return \Closure|null Registered function
362
	 */
363
	abstract public static function macro( string $name, \Closure $function = null ) : ?\Closure;
364
365
366
	/**
367
	 * Initializes the list items in the trait
368
	 *
369
	 * @param array $listItems Two dimensional associative list of domain / ID / list items that implement \Aimeos\MShop\Common\Item\Lists\Iface
370
	 * @param array $refItems Two dimensional associative list of domain / ID / domain items that implement \Aimeos\MShop\Common\Item\Iface
371
	 */
372
	protected function initListItems( array $listItems, array $refItems )
373
	{
374
		$this->listItems = $listItems;
375
		$this->listRefItems = $refItems;
376
377
		foreach( $listItems as $list ) {
378
			$this->listMax += count( $list );
379
		}
380
	}
381
382
383
	/**
384
	 * Sorts the list items according to their position value and attaches the referenced item
385
	 */
386
	protected function prepareListItems()
387
	{
388
		if( $this->listPrepared === true ) {
389
			return;
390
		}
391
392
		foreach( $this->listItems as $domain => $list )
393
		{
394
			foreach( $list as $listItem )
395
			{
396
				$refId = $listItem->getRefId();
397
398
				if( isset( $this->listRefItems[$domain][$refId] ) ) {
399
					$listItem->setRefItem( $this->listRefItems[$domain][$refId] );
400
				}
401
			}
402
403
			uasort( $this->listItems[$domain], fn( $a, $b ) => $a->getPosition() <=> $b->getPosition() );
404
		}
405
406
		$this->listPrepared = true;
407
	}
408
}
409