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

Bundle   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 114
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 6
lcom 1
cbo 6
dl 0
loc 114
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
B addProduct() 0 40 2
B addBundleProducts() 0 39 4
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