Passed
Push — master ( 5788d1...3b28d2 )
by Aimeos
04:17
created

Base   F

Complexity

Total Complexity 77

Size/Duplication

Total Lines 570
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 77
eloc 120
c 0
b 0
f 0
dl 0
loc 570
rs 2.24

34 Methods

Rating   Name   Duplication   Size   Complexity  
A __set() 0 3 1
A __clone() 0 2 1
A __isset() 0 3 1
A __construct() 0 4 1
A __get() 0 3 1
A assign() 0 7 2
A editor() 0 3 1
A getTimeModified() 0 3 1
A toArray() 0 20 4
A checkLanguageId() 0 23 6
A offsetUnset() 0 3 1
A checkDateOnlyFormat() 0 12 4
A getId() 0 9 3
A getTimeCreated() 0 3 1
A offsetExists() 0 3 1
A checkCode() 0 11 3
A __toString() 0 3 1
B fromArray() 0 17 7
A checkCurrencyId() 0 16 5
A checkCountryId() 0 16 5
B set() 0 12 7
A setModified() 0 4 1
A checkId() 0 3 2
A isAvailable() 0 3 1
A offsetGet() 0 4 1
A getSitePath() 0 11 2
A setId() 0 11 2
A isModified() 0 3 1
A jsonSerialize() 0 4 1
A setAvailable() 0 4 1
A getSiteId() 0 3 1
A offsetSet() 0 3 1
A get() 0 7 2
A checkDateFormat() 0 18 4

How to fix   Complexity   

Complex Class

Complex classes like Base 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 Base, 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 Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package MShop
8
 * @subpackage Common
9
 */
10
11
12
namespace Aimeos\MShop\Common\Item;
13
14
15
/**
16
 * Common methods for all item objects.
17
 *
18
 * @package MShop
19
 * @subpackage Common
20
 */
21
abstract class Base
22
	extends \Aimeos\MW\Common\Item\Base
23
	implements \Aimeos\MShop\Common\Item\Iface, \Aimeos\Macro\Iface, \ArrayAccess, \JsonSerializable
24
{
25
	use \Aimeos\Macro\Macroable;
26
27
	private $available = true;
28
	private $modified = false;
29
	private $prefix;
30
31
	// protected due to PHP serialization
32
	protected $bdata;
33
34
35
	/**
36
	 * Initializes the class properties.
37
	 *
38
	 * @param string $prefix Prefix for the keys returned by toArray()
39
	 * @param array $values Associative list of key/value pairs of the item properties
40
	 */
41
	public function __construct( string $prefix, array $values )
42
	{
43
		$this->prefix = (string) $prefix;
44
		$this->bdata = $values;
45
	}
46
47
48
	/**
49
	 * Creates a deep clone of all objects
50
	 */
51
	public function __clone()
52
	{
53
	}
54
55
56
	/**
57
	 * Returns the item property for the given name
58
	 *
59
	 * @param string $name Name of the property
60
	 * @return mixed|null Property value or null if property is unknown
61
	 */
62
	public function __get( string $name )
63
	{
64
		return $this->get( $name );
65
	}
66
67
68
	/**
69
	 * Tests if the item property for the given name is available
70
	 *
71
	 * @param string $name Name of the property
72
	 * @return bool True if the property exists, false if not
73
	 */
74
	public function __isset( string $name ) : bool
75
	{
76
		return array_key_exists( $name, $this->bdata );
77
	}
78
79
80
	/**
81
	 * Sets the new item property for the given name
82
	 *
83
	 * @param string $name Name of the property
84
	 * @param mixed $value New property value
85
	 */
86
	public function __set( string $name, $value )
87
	{
88
		$this->set( $name, $value );
89
	}
90
91
92
	/**
93
	 * Specifies the data which should be serialized to JSON by json_encode().
94
	 *
95
	 * @return array<string,mixed> Data to serialize to JSON
96
	 */
97
	#[\ReturnTypeWillChange]
98
	public function jsonSerialize()
99
	{
100
		return $this->bdata;
101
	}
102
103
104
	/**
105
	 * Tests if the item property for the given name is available
106
	 *
107
	 * @param string $name Name of the property
108
	 * @return bool True if the property exists, false if not
109
	 */
110
	public function offsetExists( $name ) : bool
111
	{
112
		return array_key_exists( $name, $this->bdata );
113
	}
114
115
116
	/**
117
	 * Returns the item property for the given name
118
	 *
119
	 * @param string $name Name of the property
120
	 * @return mixed|null Property value or null if property is unknown
121
	 */
122
	#[\ReturnTypeWillChange]
123
	public function offsetGet( $name )
124
	{
125
		return $this->get( $name );
126
	}
127
128
129
	/**
130
	 * Sets the new item property for the given name
131
	 *
132
	 * @param string $name Name of the property
133
	 * @param mixed $value New property value
134
	 */
135
	public function offsetSet( $name, $value ) : void
136
	{
137
		$this->set( $name, $value );
138
	}
139
140
141
	/**
142
	 * Removes an item property
143
	 * This is not supported by items
144
	 *
145
	 * @param string $name Name of the property
146
	 * @throws \LogicException Always thrown because this method isn't supported
147
	 */
148
	public function offsetUnset( $name ) : void
149
	{
150
		throw new \LogicException( 'Not implemented' );
151
	}
152
153
154
	/**
155
	 * Returns the ID of the items
156
	 *
157
	 * @return string ID of the item or an empty string
158
	 */
159
	public function __toString() : string
160
	{
161
		return (string) $this->getId();
162
	}
163
164
165
	/**
166
	 * Assigns multiple key/value pairs to the item
167
	 *
168
	 * @param iterable $pairs Associative list of key/value pairs
169
	 * @return \Aimeos\MShop\Common\Item\Iface Item for method chaining
170
	 */
171
	public function assign( iterable $pairs ) : \Aimeos\MShop\Common\Item\Iface
172
	{
173
		foreach( $pairs as $key => $value ) {
174
			$this->set( $key, $value );
175
		}
176
177
		return $this;
178
	}
179
180
181
	/**
182
	 * Returns the item property for the given name
183
	 *
184
	 * @param string $name Name of the property
185
	 * @param mixed $default Default value if property is unknown
186
	 * @return mixed|null Property value or default value if property is unknown
187
	 */
188
	public function get( string $name, $default = null )
189
	{
190
		if( array_key_exists( $name, $this->bdata ) ) {
191
			return $this->bdata[$name];
192
		}
193
194
		return $default;
195
	}
196
197
198
	/**
199
	 * Sets the new item property for the given name
200
	 *
201
	 * @param string $name Name of the property
202
	 * @param mixed $value New property value
203
	 * @return \Aimeos\MShop\Common\Item\Iface Item for method chaining
204
	 */
205
	public function set( string $name, $value ) : \Aimeos\MShop\Common\Item\Iface
206
	{
207
		// workaround for NULL values instead of empty strings and stringified integers from database
208
		if( !array_key_exists( $name, $this->bdata ) || $this->bdata[$name] != $value
209
			|| $value === null && $this->bdata[$name] !== null
210
			|| $value !== null && $this->bdata[$name] === null
211
		) {
212
			$this->bdata[$name] = $value;
213
			$this->setModified();
214
		}
215
216
		return $this;
217
	}
218
219
220
	/**
221
	 * Returns the ID of the item if available.
222
	 *
223
	 * @return string|null ID of the item
224
	 */
225
	public function getId() : ?string
226
	{
227
		$key = $this->prefix . 'id';
228
229
		if( isset( $this->bdata[$key] ) && $this->bdata[$key] != '' ) {
230
			return (string) $this->bdata[$key];
231
		}
232
233
		return null;
234
	}
235
236
237
	/**
238
	 * Sets the new ID of the item.
239
	 *
240
	 * @param string|null $id ID of the item
241
	 * @return \Aimeos\MShop\Common\Item\Iface Item for chaining method calls
242
	 */
243
	public function setId( ?string $id ) : \Aimeos\MShop\Common\Item\Iface
244
	{
245
		$key = $this->prefix . 'id';
246
247
		if( ( $this->bdata[$key] = $this->checkId( $this->getId(), $id ) ) === null ) {
248
			$this->modified = true;
249
		} else {
250
			$this->modified = false;
251
		}
252
253
		return $this;
254
	}
255
256
257
	/**
258
	 * Returns the site ID of the item.
259
	 *
260
	 * @return string Site ID or null if no site id is available
261
	 */
262
	public function getSiteId() : string
263
	{
264
		return $this->get( $this->prefix . 'siteid', $this->get( 'siteid', '' ) );
265
	}
266
267
268
	/**
269
	 * Returns the list site IDs up to the root site item.
270
	 *
271
	 * @return array List of site IDs
272
	 */
273
	public function getSitePath() : array
274
	{
275
		$pos = 0;
276
		$list = [];
277
		$siteId = $this->getSiteId();
278
279
		while( ( $pos = strpos( $siteId, '.', $pos ) ) !== false ) {
280
			$list[] = substr( $siteId, 0, ++$pos );
281
		}
282
283
		return $list;
284
	}
285
286
287
	/**
288
	 * Returns modify date/time of the order coupon.
289
	 *
290
	 * @return string|null Modification time (YYYY-MM-DD HH:mm:ss)
291
	 */
292
	public function getTimeModified() : ?string
293
	{
294
		return $this->get( $this->prefix . 'mtime', $this->get( 'mtime' ) );
295
	}
296
297
298
	/**
299
	 * Returns the create date of the item.
300
	 *
301
	 * @return string|null ISO date in YYYY-MM-DD hh:mm:ss format
302
	 */
303
	public function getTimeCreated() : ?string
304
	{
305
		return $this->get( $this->prefix . 'ctime', $this->get( 'ctime' ) );
306
	}
307
308
309
	/**
310
	 * Returns the name of editor who created/modified the item at last.
311
	 *
312
	 * @return string Name of editor who created/modified the item at last
313
	 */
314
	public function editor() : string
315
	{
316
		return $this->get( $this->prefix . 'editor', $this->get( 'editor', '' ) );
317
	}
318
319
320
	/**
321
	 * Tests if the item is available based on status, time, language and currency
322
	 *
323
	 * @return bool True if available, false if not
324
	 */
325
	public function isAvailable() : bool
326
	{
327
		return $this->available;
328
	}
329
330
331
	/**
332
	 * Sets the general availability of the item
333
	 *
334
	 * @return bool $value True if available, false if not
335
	 * @return \Aimeos\MShop\Common\Item\Iface Item for chaining method calls
336
	 */
337
	public function setAvailable( bool $value ) : \Aimeos\MShop\Common\Item\Iface
338
	{
339
		$this->available = $value;
340
		return $this;
341
	}
342
343
344
	/**
345
	 * Tests if this Item object was modified.
346
	 *
347
	 * @return bool True if modified, false if not
348
	 */
349
	public function isModified() : bool
350
	{
351
		return $this->modified;
352
	}
353
354
355
	/**
356
	 * Sets the modified flag of the object.
357
	 *
358
	 * @return \Aimeos\MShop\Common\Item\Iface Item for chaining method calls
359
	 */
360
	public function setModified() : \Aimeos\MShop\Common\Item\Iface
361
	{
362
		$this->modified = true;
363
		return $this;
364
	}
365
366
367
	/**
368
	 * Sets the item values from the given array and removes that entries from the list
369
	 *
370
	 * @param array $list Associative list of item keys and their values
371
	 * @param bool True to set private properties too, false for public only
372
	 * @return \Aimeos\MShop\Common\Item\Iface Item for chaining method calls
373
	 */
374
	public function fromArray( array &$list, bool $private = false ) : \Aimeos\MShop\Common\Item\Iface
375
	{
376
		if( $private && array_key_exists( $this->prefix . 'id', $list ) )
377
		{
378
			$this->setId( $list[$this->prefix . 'id'] );
379
			unset( $list[$this->prefix . 'id'] );
380
		}
381
382
		// Add custom columns
383
		foreach( $list as $key => $value )
384
		{
385
			if( ( $value === null || is_scalar( $value ) ) && strpos( $key, '.' ) === false ) {
386
				$this->set( $key, $value );
387
			}
388
		}
389
390
		return $this;
391
	}
392
393
394
	/**
395
	 * Returns the item values as array.
396
	 *
397
	 * @param bool True to return private properties, false for public only
398
	 * @return array Associative list of item properties and their values
399
	 */
400
	public function toArray( bool $private = false ) : array
401
	{
402
		$list = [$this->prefix . 'id' => $this->getId()];
403
404
		if( $private === true )
405
		{
406
			$list[$this->prefix . 'siteid'] = $this->getSiteId();
407
			$list[$this->prefix . 'ctime'] = $this->getTimeCreated();
408
			$list[$this->prefix . 'mtime'] = $this->getTimeModified();
409
			$list[$this->prefix . 'editor'] = $this->editor();
410
		}
411
412
		foreach( $this->bdata as $key => $value )
413
		{
414
			if( strpos( $key, '.' ) === false ) {
415
				$list[$key] = $value;
416
			}
417
		}
418
419
		return $list;
420
	}
421
422
423
	/**
424
	 * Checks if the new ID is valid for the item.
425
	 *
426
	 * @param string|null $old Current ID of the item
427
	 * @param string|null $new New ID which should be set in the item
428
	 * @return string|null Value of the new ID
429
	 */
430
	public static function checkId( ?string $old, ?string $new ) : ?string
431
	{
432
		return ( $new !== null ? (string) $new : $new );
433
	}
434
435
436
	/**
437
	 * Tests if the date parameter represents an ISO format.
438
	 *
439
	 * @param string|null $date ISO date in yyyy-mm-dd HH:ii:ss format or null
440
	 * @return string|null Clean date or null for no date
441
	 * @throws \Aimeos\MShop\Exception If the date is invalid
442
	 */
443
	protected function checkDateFormat( ?string $date ) : ?string
444
	{
445
		$regex = '/^[0-9]{4}-[0-1][0-9]-[0-3][0-9](( |T)[0-2][0-9]:[0-5][0-9](:[0-5][0-9])?)?$/';
446
447
		if( $date != null )
448
		{
449
			if( preg_match( $regex, (string) $date ) !== 1 ) {
450
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid characters in date, ISO format "YYYY-MM-DD hh:mm:ss" expected' ) );
451
			}
452
453
			if( strlen( $date ) === 16 ) {
454
				$date .= ':00';
455
			}
456
457
			return str_replace( 'T', ' ', (string) $date );
458
		}
459
460
		return null;
461
	}
462
463
464
	/**
465
	 * Tests if the date param represents an ISO format.
466
	 *
467
	 * @param string|null $date ISO date in YYYY-MM-DD format or null for no date
468
	 */
469
	protected function checkDateOnlyFormat( ?string $date ) : ?string
470
	{
471
		if( $date !== null && $date !== '' )
472
		{
473
			if( preg_match( '/^[0-9]{4}-[0-1][0-9]-[0-3][0-9]$/', (string) $date ) !== 1 ) {
474
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid characters in date, ISO format "YYYY-MM-DD" expected' ) );
475
			}
476
477
			return (string) $date;
478
		}
479
480
		return null;
481
	}
482
483
484
	/**
485
	 * Tests if the code is valid.
486
	 *
487
	 * @param string $code New code for an item
488
	 * @param int $length Number of allowed characters
489
	 * @return string Item code
490
	 * @throws \Aimeos\MShop\Exception If the code is invalid
491
	 */
492
	protected function checkCode( string $code, int $length = 64 ) : string
493
	{
494
		if( strlen( $code ) > $length ) {
495
			throw new \Aimeos\MShop\Exception( sprintf( 'Code is too long' ) );
496
		}
497
498
		if( preg_match( '/[ \x{0000}\x{0009}\x{000A}\x{000C}\x{000D}\x{0085}]+/u', $code ) === 1 ) {
499
			throw new \Aimeos\MShop\Exception( sprintf( 'Code contains invalid characters: "%1$s"', $code ) );
500
		}
501
502
		return (string) $code;
503
	}
504
505
506
	/**
507
	 * Tests if the country ID parameter represents an ISO country format.
508
	 *
509
	 * @param string|null $countryid Two letter ISO country format, e.g. DE
510
	 * @param bool $null True if null is allowed, false if not
511
	 * @return string|null Two letter ISO country ID or null for no country
512
	 * @throws \Aimeos\MShop\Exception If the country ID is invalid
513
	 */
514
	protected function checkCountryId( ?string $countryid, bool $null = true ) : ?string
515
	{
516
		if( $null === false && $countryid == null ) {
517
			throw new \Aimeos\MShop\Exception( sprintf( 'Invalid ISO country code' ) );
518
		}
519
520
		if( $countryid != null )
521
		{
522
			if( preg_match( '/^[A-Za-z]{2}$/', $countryid ) !== 1 ) {
523
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid ISO country code' ) );
524
			}
525
526
			return strtoupper( $countryid );
527
		}
528
529
		return null;
530
	}
531
532
533
	/**
534
	 * Tests if the currency ID parameter represents an ISO currency format.
535
	 *
536
	 * @param string|null $currencyid Three letter ISO currency format, e.g. EUR
537
	 * @param bool $null True if null is allowed, false if not
538
	 * @return string|null Three letter ISO currency ID or null for no currency
539
	 * @throws \Aimeos\MShop\Exception If the currency ID is invalid
540
	 */
541
	protected function checkCurrencyId( ?string $currencyid, bool $null = true ) : ?string
542
	{
543
		if( $null === false && $currencyid == null ) {
544
			throw new \Aimeos\MShop\Exception( sprintf( 'Invalid ISO currency code' ) );
545
		}
546
547
		if( $currencyid != null )
548
		{
549
			if( preg_match( '/^[A-Z]{3}$/', $currencyid ) !== 1 ) {
550
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid ISO currency code' ) );
551
			}
552
553
			return strtoupper( $currencyid );
554
		}
555
556
		return null;
557
	}
558
559
560
	/**
561
	 * Tests if the language ID parameter represents an ISO language format.
562
	 *
563
	 * @param string|null $langid ISO language format, e.g. de or de_DE
564
	 * @param bool $null True if null is allowed, false if not
565
	 * @return string|null ISO language ID or null for no language
566
	 * @throws \Aimeos\MShop\Exception If the language ID is invalid
567
	 */
568
	protected function checkLanguageId( ?string $langid, bool $null = true ) : ?string
569
	{
570
		if( $null === false && $langid == null ) {
571
			throw new \Aimeos\MShop\Exception( sprintf( 'Invalid ISO language code' ) );
572
		}
573
574
		if( $langid != null )
575
		{
576
			if( preg_match( '/^[a-zA-Z]{2}(_[a-zA-Z]{2})?$/', $langid ) !== 1 ) {
577
				throw new \Aimeos\MShop\Exception( sprintf( 'Invalid ISO language code' ) );
578
			}
579
580
			$parts = explode( '_', $langid );
581
			$parts[0] = strtolower( $parts[0] );
582
583
			if( isset( $parts[1] ) ) {
584
				$parts[1] = strtoupper( $parts[1] );
585
			}
586
587
			return implode( '_', $parts );
588
		}
589
590
		return null;
591
	}
592
}
593