Completed
Push — master ( b46c0d...fe698d )
by Aimeos
06:27
created

Bundle::addProduct()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 26
nc 2
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017
6
 * @package Controller
7
 * @subpackage Frontend
8
 */
9
10
11
namespace Aimeos\Controller\Frontend\Basket\Decorator;
12
13
14
/**
15
 * Bundle product handling
16
 *
17
 * @package Controller
18
 * @subpackage Frontend
19
 */
20
class Bundle
21
	extends \Aimeos\Controller\Frontend\Basket\Decorator\Base
2 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
Coding Style introduced by
Expected 0 spaces between "Base" and comma; 1 found
Loading history...
22
	implements \Aimeos\Controller\Frontend\Basket\Iface, \Aimeos\Controller\Frontend\Common\Decorator\Iface
1 ignored issue
show
Coding Style introduced by
The implements keyword must be on the same line as the class name
Loading history...
23
{
24
	/**
25
	 * Adds a bundle product to the basket of the user stored in the session.
26
	 *
27
	 * @param string $prodid ID of the base product to add
28
	 * @param integer $quantity Amount of products that should by added
29
	 * @param array $options Possible options are: 'stock'=>true|false and 'variant'=>true|false
30
	 * 	The 'stock'=>false option allows adding products without being in stock.
31
	 * 	The 'variant'=>false option allows adding the selection product to the basket
32
	 * 	instead of the specific sub-product if the variant-building attribute IDs
33
	 * 	doesn't match a specific sub-product or if the attribute IDs are missing.
34
	 * @param array $variantAttributeIds List of variant-building attribute IDs that identify a specific product
35
	 * 	in a selection products
36
	 * @param array $configAttributeIds  List of attribute IDs that doesn't identify a specific product in a
37
	 * 	selection of products but are stored together with the product (e.g. for configurable products)
38
	 * @param array $hiddenAttributeIds List of attribute IDs that should be stored along with the product in the order
39
	 * @param array $customAttributeValues Associative list of attribute IDs and arbitrary values that should be stored
40
	 * 	along with the product in the order
41
	 * @param string $stocktype Unique code of the stock type to deliver the products from
42
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
43
	 */
44
	public function addProduct( $prodid, $quantity = 1, array $options = array(), array $variantAttributeIds = array(),
45
		array $configAttributeIds = array(), array $hiddenAttributeIds = array(), array $customAttributeValues = array(),
46
		$stocktype = 'default' )
47
	{
48
		$context = $this->getContext();
49
		$productManager = \Aimeos\MShop\Factory::createManager( $context, 'product' );
50
		$productItem = $productManager->getItem( $prodid, array( 'media', 'supplier', 'price', 'product', 'text' ) );
51
52
		if( $productItem->getType() !== 'bundle' )
53
		{
54
			return $this->getController()->addProduct(
55
				$prodid, $quantity, $options, $variantAttributeIds, $configAttributeIds,
56
				$hiddenAttributeIds, $customAttributeValues, $stocktype
57
			);
58
		}
59
60
		$orderBaseProductItem = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' )->createItem();
61
		$orderBaseProductItem->copyFrom( $productItem );
62
		$orderBaseProductItem->setQuantity( $quantity );
63
		$orderBaseProductItem->setStockType( $stocktype );
64
65
		$prices = $productItem->getRefItems( 'price', 'default', 'default' );
66
		$this->addBundleProducts( $orderBaseProductItem, $productItem, $variantAttributeIds, $stocktype );
67
68
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
69
		$price = $priceManager->getLowestPrice( $prices, $quantity );
70
71
		$attr = $this->createOrderProductAttributes( $price, $prodid, $quantity, $configAttributeIds, 'config' );
72
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $hiddenAttributeIds, 'hidden' ) );
73
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, array_keys( $customAttributeValues ), 'custom', $customAttributeValues ) );
74
75
		// remove product rebate of original price in favor to rebates granted for the order
76
		$price->setRebate( '0.00' );
77
78
		$orderBaseProductItem->setPrice( $price );
79
		$orderBaseProductItem->setAttributes( $attr );
80
81
		$this->getController()->get()->addProduct( $orderBaseProductItem );
82
		$this->getController()->save();
83
	}
84
85
86
	/**
87
	 * Adds the bundled products to the order product item.
88
	 *
89
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Order product item
90
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Bundle product item
91
	 * @param array $variantAttributeIds List of product variant attribute IDs
92
	 * @param string $stocktype
93
	 */
94
	protected function addBundleProducts( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
95
		\Aimeos\MShop\Product\Item\Iface $productItem, array $variantAttributeIds, $stocktype )
96
	{
97
		$quantity = $orderBaseProductItem->getQuantity();
98
		$products = $subProductIds = $orderProducts = array();
99
		$orderProductManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product' );
100
101
		foreach( $productItem->getRefItems( 'product', null, 'default' ) as $item ) {
102
			$subProductIds[] = $item->getId();
103
		}
104
105
		if( count( $subProductIds ) > 0 )
106
		{
107
			$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
108
109
			$search = $productManager->createSearch( true );
110
			$expr = array(
111
				$search->compare( '==', 'product.id', $subProductIds ),
112
				$search->getConditions(),
113
			);
114
			$search->setConditions( $search->combine( '&&', $expr ) );
115
116
			$products = $productManager->searchItems( $search, array( 'attribute', 'media', 'price', 'text' ) );
117
		}
118
119
		foreach( $products as $product )
120
		{
121
			$prices = $product->getRefItems( 'price', 'default', 'default' );
122
123
			$orderProduct = $orderProductManager->createItem();
124
			$orderProduct->copyFrom( $product );
125
			$orderProduct->setStockType( $stocktype );
126
			$orderProduct->setPrice( $this->calcPrice( $orderProduct, $prices, $quantity ) );
127
128
			$orderProducts[] = $orderProduct;
129
		}
130
131
		$orderBaseProductItem->setProducts( $orderProducts );
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Order\Item\Base\Product\Iface as the method setProducts() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Product\Standard.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
132
	}
133
}
134