Completed
Push — master ( 207311...06e599 )
by Aimeos
09:29
created

Base   C

Complexity

Total Complexity 63

Size/Duplication

Total Lines 417
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 11

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 417
rs 5.8893
wmc 63
lcom 3
cbo 11

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A updateCoupon() 0 11 2
A deleteCoupon() 0 4 1
A checkConfigBE() 0 4 1
A getConfigBE() 0 4 1
A isAvailable() 0 4 1
A setObject() 0 5 1
C checkConfig() 0 74 30
A getCode() 0 4 1
A getConfigItems() 0 10 2
A getConfigValue() 0 10 2
A getContext() 0 4 1
A getItemBase() 0 4 1
A getObject() 0 8 2
B createProduct() 0 31 3
B createMonetaryRebateProducts() 0 45 6
C getPriceByTaxRate() 0 45 7

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

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2012
6
 * @copyright Aimeos (aimeos.org), 2015-2016
7
 * @package MShop
8
 * @subpackage Coupon
9
 */
10
11
12
namespace Aimeos\MShop\Coupon\Provider;
13
14
15
/**
16
 * Abstract model for coupons.
17
 *
18
 * @package MShop
19
 * @subpackage Coupon
20
 */
21
abstract class Base implements Iface
22
{
23
	private $context;
24
	private $object;
25
	private $item;
26
	private $code = '';
27
28
	/**
29
	 * Initializes the coupon model.
30
	 *
31
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
32
	 * @param \Aimeos\MShop\Coupon\Item\Iface $item Coupon item to set
33
	 * @param string $code Coupon code entered by the customer
34
	 */
35
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Coupon\Item\Iface $item, $code )
36
	{
37
		$this->context = $context;
38
		$this->item = $item;
39
		$this->code = $code;
40
	}
41
42
43
	/**
44
	 * Updates the result of a coupon to the order base instance.
45
	 *
46
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $base Basic order of the customer
47
	 */
48
	public function updateCoupon( \Aimeos\MShop\Order\Item\Base\Iface $base )
49
	{
50
		if( $this->getObject()->isAvailable( $base ) !== true )
51
		{
52
			$base->deleteCoupon( $this->code );
53
			return;
54
		}
55
56
		$this->deleteCoupon( $base );
57
		$this->addCoupon( $base );
58
	}
59
60
61
	/**
62
	 * Removes the result of a coupon from the order base instance.
63
	 *
64
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $base Basic order of the customer
65
	 */
66
	public function deleteCoupon( \Aimeos\MShop\Order\Item\Base\Iface $base )
67
	{
68
		$base->deleteCoupon( $this->code, true );
69
	}
70
71
	/**
72
	 * Checks the backend configuration attributes for validity.
73
	 *
74
	 * @param array $attributes Attributes added by the shop owner in the administraton interface
75
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
76
	 * 	known by the provider but aren't valid resp. null for attributes whose values are OK
77
	 */
78
	public function checkConfigBE( array $attributes )
79
	{
80
		return [];
81
	}
82
83
84
	/**
85
	 * Returns the configuration attribute definitions of the provider to generate a list of available fields and
86
	 * rules for the value of each field in the administration interface.
87
	 *
88
	 * @return array List of attribute definitions implementing \Aimeos\MW\Common\Critera\Attribute\Iface
89
	 */
90
	public function getConfigBE()
91
	{
92
		return [];
93
	}
94
95
96
	/**
97
	 * Tests if a coupon should be granted
98
	 *
99
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $base
100
	 */
101
	public function isAvailable( \Aimeos\MShop\Order\Item\Base\Iface $base )
102
	{
103
		return true;
104
	}
105
106
107
	/**
108
	 * Injects the reference of the outmost object
109
	 *
110
	 * @param \Aimeos\MShop\Coupon\Provider\Iface $object Reference to the outmost provider or decorator
111
	 * @return \Aimeos\MShop\Coupon\Provider\Iface Coupon object for chaining method calls
112
	 */
113
	public function setObject( \Aimeos\MShop\Coupon\Provider\Iface $object )
114
	{
115
		$this->object = $object;
116
		return $this;
117
	}
118
119
120
	/**
121
	 * Checks required fields and the types of the config array.
122
	 *
123
	 * @param array $config Config parameters
124
	 * @param array $attributes Attributes for the config array
125
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
126
	 * 	known by the provider but aren't valid resp. null for attributes whose values are OK
127
	 */
128
	protected function checkConfig( array $config, array $attributes )
129
	{
130
		$errors = [];
131
132
		foreach( $config as $key => $def )
133
		{
134
			if( $def['required'] === true && ( !isset( $attributes[$key] ) || $attributes[$key] === '' ) )
135
			{
136
				$errors[$key] = sprintf( 'Configuration for "%1$s" is missing', $key );
137
				continue;
138
			}
139
140
			if( isset( $attributes[$key] ) )
141
			{
142
				switch( $def['type'] )
143
				{
144
					case 'boolean':
145
						if( !is_string( $attributes[$key] ) || $attributes[$key] !== '0' && $attributes[$key] !== '1' ) {
146
							$errors[$key] = sprintf( 'Not a true/false value' ); continue 2;
147
						}
148
						break;
149
					case 'string':
150
						if( is_string( $attributes[$key] ) === false ) {
151
							$errors[$key] = sprintf( 'Not a string' ); continue 2;
152
						}
153
						break;
154
					case 'integer':
155
						if( ctype_digit( $attributes[$key] ) === false ) {
156
							$errors[$key] = sprintf( 'Not an integer number' ); continue 2;
157
						}
158
						break;
159
					case 'number':
160
						if( is_numeric( $attributes[$key] ) === false ) {
161
							$errors[$key] = sprintf( 'Not a number' ); continue 2;
162
						}
163
						break;
164
					case 'date':
165
						$pattern = '/^[0-9]{4}-[0-1][0-9]-[0-3][0-9]$/';
166
						if( !is_string( $attributes[$key] ) || preg_match( $pattern, $attributes[$key] ) !== 1 ) {
167
							$errors[$key] = sprintf( 'Not a date' ); continue 2;
168
						}
169
						break;
170
					case 'datetime':
171
						$pattern = '/^[0-9]{4}-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9](:[0-5][0-9])?$/';
172
						if( !is_string( $attributes[$key] ) || preg_match( $pattern, $attributes[$key] ) !== 1 ) {
173
							$errors[$key] = sprintf( 'Not a date and time' ); continue 2;
174
						}
175
						break;
176
					case 'time':
177
						$pattern = '/^([0-2])?[0-9]:[0-5][0-9](:[0-5][0-9])?$/';
178
						if( !is_string( $attributes[$key] ) || preg_match( $pattern, $attributes[$key] ) !== 1 ) {
179
							$errors[$key] = sprintf( 'Not a time' ); continue 2;
180
						}
181
						break;
182
					case 'select':
183
						if( !is_array( $def['default'] ) || !isset( $def['default'][$attributes[$key]] ) ) {
184
							$errors[$key] = sprintf( 'Not a listed value' ); continue 2;
185
						}
186
						break;
187
					case 'map':
188
						if( !is_array( $attributes[$key] ) ) {
189
							$errors[$key] = sprintf( 'Not a key/value map' ); continue 2;
190
						}
191
						break;
192
					default:
193
						throw new \Aimeos\MShop\Coupon\Exception( sprintf( 'Invalid type "%1$s"', $def['type'] ) );
194
				}
195
			}
196
197
			$errors[$key] = null;
198
		}
199
200
		return $errors;
201
	}
202
203
204
	/**
205
	 * Returns the coupon code the provider is responsible for.
206
	 *
207
	 * @return string Coupon code
208
	 */
209
	protected function getCode()
210
	{
211
		return $this->code;
212
	}
213
214
215
	/**
216
	 * Returns the criteria attribute items for the backend configuration
217
	 *
218
	 * @return \Aimeos\MW\Criteria\Attribute\Iface[] List of criteria attribute items
219
	 */
220
	protected function getConfigItems( array $configList )
221
	{
222
		$list = [];
223
224
		foreach( $configList as $key => $config ) {
225
			$list[$key] = new \Aimeos\MW\Criteria\Attribute\Standard( $config );
226
		}
227
228
		return $list;
229
	}
230
231
232
	/**
233
	 * Returns the configuration value from the service item specified by its key.
234
	 *
235
	 * @param string $key Configuration key
236
	 * @param mixed $default Default value if configuration key isn't available
237
	 * @return mixed Value from service item configuration
238
	 */
239
	protected function getConfigValue( $key, $default = null )
240
	{
241
		$config = $this->item->getConfig();
242
243
		if( isset( $config[$key] ) ) {
244
			return $config[$key];
245
		}
246
247
		return $default;
248
	}
249
250
251
	/**
252
	 * Returns the stored context object.
253
	 *
254
	 * @return \Aimeos\MShop\Context\Item\Iface Context object
255
	 */
256
	protected function getContext()
257
	{
258
		return $this->context;
259
	}
260
261
262
	/**
263
	 * Returns the stored coupon item.
264
	 *
265
	 * @return \Aimeos\MShop\Coupon\Item\Iface Coupon item
266
	 */
267
	protected function getItemBase()
268
	{
269
		return $this->item;
270
	}
271
272
273
	/**
274
	 * Returns the outmost decorator of the decorator stack
275
	 *
276
	 * @return \Aimeos\MShop\Coupon\Provider\Iface Outmost decorator object
277
	 */
278
	protected function getObject()
279
	{
280
		if( $this->object !== null ) {
281
			return $this->object;
282
		}
283
284
		return $this;
285
	}
286
287
288
	/**
289
	 * Creates an order product from the product item.
290
	 *
291
	 * @param string $productCode Unique product code
292
	 * @param integer $quantity Number of products in basket
293
	 * @param string $stockType Unique code of the stock type the product is from
294
	 * @return \Aimeos\MShop\Order\Item\Base\Product\Iface Ordered product
295
	 */
296
	protected function createProduct( $productCode, $quantity = 1, $stockType = 'default' )
297
	{
298
		$productManager = \Aimeos\MShop\Factory::createManager( $this->context, 'product' );
299
		$search = $productManager->createSearch( true );
300
		$search->setConditions( $search->compare( '==', 'product.code', $productCode ) );
301
		$products = $productManager->searchItems( $search, array( 'text', 'media', 'price' ) );
302
303
		if( ( $product = reset( $products ) ) === false ) {
304
			throw new \Aimeos\MShop\Coupon\Exception( sprintf( 'No product with code "%1$s" found', $productCode ) );
305
		}
306
307
		$priceManager = \Aimeos\MShop\Factory::createManager( $this->context, 'price' );
308
		$prices = $product->getRefItems( 'price', 'default', 'default' );
309
310
		if( empty( $prices ) ) {
311
			$price = $priceManager->createItem();
312
		} else {
313
			$price = $priceManager->getLowestPrice( $prices, $quantity );
314
		}
315
316
		$orderBaseProductManager = \Aimeos\MShop\Factory::createManager( $this->context, 'order/base/product' );
317
		$orderProduct = $orderBaseProductManager->createItem();
318
319
		$orderProduct->copyFrom( $product );
320
		$orderProduct->setQuantity( $quantity );
321
		$orderProduct->setStockType( $stockType );
322
		$orderProduct->setPrice( $price );
323
		$orderProduct->setFlags( \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE );
324
325
		return $orderProduct;
326
	}
327
328
329
	/**
330
	 * Creates the order products for monetary rebates.
331
	 *
332
	 * @param \Aimeos\MShop\Order\Item\Base\Iface Basket object
333
	 * @param string $productCode Unique product code
334
	 * @param float $rebate Rebate amount that should be granted
335
	 * @param integer $quantity Number of products in basket
336
	 * @param string $stockType Unique code of the stock type the product is from
337
	 * @return \Aimeos\MShop\Order\Item\Base\Product\Iface[] Order products with monetary rebates
338
	 */
339
	protected function createMonetaryRebateProducts( \Aimeos\MShop\Order\Item\Base\Iface $base,
340
		$productCode, $rebate, $quantity = 1, $stockType = 'default' )
341
	{
342
		$orderProducts = [];
343
		$prices = $this->getPriceByTaxRate( $base );
344
345
		krsort( $prices );
346
347
		if( empty( $prices ) ) {
348
			$prices = array( '0.00' => \Aimeos\MShop\Factory::createManager( $this->getContext(), 'price' )->createItem() );
349
		}
350
351
		foreach( $prices as $taxrate => $price )
352
		{
353
			if( abs( $rebate ) < 0.01 ) {
354
				break;
355
			}
356
357
			$amount = $price->getValue() + $price->getCosts();
358
359
			if( $amount > 0 && $amount < $rebate )
360
			{
361
				$value = $price->getValue() + $price->getCosts();
362
				$rebate -= $value;
363
			}
364
			else
365
			{
366
				$value = $rebate;
367
				$rebate = '0.00';
368
			}
369
370
			$orderProduct = $this->createProduct( $productCode, $quantity, $stockType );
371
372
			$price = $orderProduct->getPrice();
373
			$price->setValue( -$value );
374
			$price->setRebate( $value );
375
			$price->setTaxRate( $taxrate );
376
377
			$orderProduct->setPrice( $price );
378
379
			$orderProducts[] = $orderProduct;
380
		}
381
382
		return $orderProducts;
383
	}
384
385
386
	/**
387
	 * Returns a list of tax rates and their price items for the given basket.
388
	 *
389
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket containing the products, services, etc.
390
	 * @return array Associative list of tax rates as key and corresponding price items as value
391
	 */
392
	protected function getPriceByTaxRate( \Aimeos\MShop\Order\Item\Base\Iface $basket )
393
	{
394
		$taxrates = [];
395
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'price' );
396
397
		foreach( $basket->getProducts() as $product )
398
		{
399
			$price = $product->getPrice();
400
			$taxrate = $price->getTaxRate();
401
402
			if( !isset( $taxrates[$taxrate] ) ) {
403
				$taxrates[$taxrate] = $manager->createItem();
404
			}
405
406
			$taxrates[$taxrate]->addItem( $price, $product->getQuantity() );
407
		}
408
409
		try
410
		{
411
			$price = $basket->getService( 'delivery' )->getPrice();
412
			$taxrate = $price->getTaxRate();
413
414
			if( !isset( $taxrates[$taxrate] ) ) {
415
				$taxrates[$taxrate] = $manager->createItem();
416
			}
417
418
			$taxrates[$taxrate]->addItem( $price );
419
		}
420
		catch( \Exception $e ) { ; } // if delivery service isn't available
421
422
		try
423
		{
424
			$price = $basket->getService( 'payment' )->getPrice();
425
			$taxrate = $price->getTaxRate();
426
427
			if( !isset( $taxrates[$taxrate] ) ) {
428
				$taxrates[$taxrate] = $manager->createItem();
429
			}
430
431
			$taxrates[$taxrate]->addItem( $price );
432
		}
433
		catch( \Exception $e ) { ; } // if payment service isn't available
434
435
		return $taxrates;
436
	}
437
}
438