Completed
Push — master ( 137f58...aece78 )
by Aimeos
02:30
created

Standard::addProduct()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 42
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 42
rs 8.8571
cc 3
eloc 28
nc 3
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 Metaways Infosystems GmbH, 2012
6
 * @copyright Aimeos (aimeos.org), 2015-2016
7
 * @package Controller
8
 * @subpackage Frontend
9
 */
10
11
12
namespace Aimeos\Controller\Frontend\Basket;
13
14
15
/**
16
 * Default implementation of the basket frontend controller.
17
 *
18
 * @package Controller
19
 * @subpackage Frontend
20
 */
21
class Standard extends Base implements Iface, \Aimeos\Controller\Frontend\Common\Iface
22
{
23
	private $basket;
24
	private $domainManager;
25
26
27
	/**
28
	 * Initializes the frontend controller.
29
	 *
30
	 * @param \Aimeos\MShop\Context\Item\Iface $context Object storing the required instances for manaing databases
31
	 *  connections, logger, session, etc.
32
	 */
33
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
34
	{
35
		parent::__construct( $context );
36
37
		$this->domainManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base' );
38
		$this->basket = $this->domainManager->getSession();
39
40
		$this->checkLocale();
41
	}
42
43
44
	/**
45
	 * Empties the basket and removing all products, addresses, services, etc.
46
	 */
47
	public function clear()
48
	{
49
		$this->basket = $this->domainManager->createItem();
50
		$this->domainManager->setSession( $this->basket );
51
	}
52
53
54
	/**
55
	 * Returns the basket object.
56
	 *
57
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket holding products, addresses and delivery/payment options
58
	 */
59
	public function get()
60
	{
61
		return $this->basket;
62
	}
63
64
65
	/**
66
	 * Adds a categorized product to the basket of the user stored in the session.
67
	 *
68
	 * @param string $prodid ID of the base product to add
69
	 * @param integer $quantity Amount of products that should by added
70
	 * @param array $options Possible options are: 'stock'=>true|false and 'variant'=>true|false
71
	 * 	The 'stock'=>false option allows adding products without being in stock.
72
	 * 	The 'variant'=>false option allows adding the selection product to the basket
73
	 * 	instead of the specific sub-product if the variant-building attribute IDs
74
	 * 	doesn't match a specific sub-product or if the attribute IDs are missing.
75
	 * @param array $variantAttributeIds List of variant-building attribute IDs that identify a specific product
76
	 * 	in a selection products
77
	 * @param array $configAttributeIds  List of attribute IDs that doesn't identify a specific product in a
78
	 * 	selection of products but are stored together with the product (e.g. for configurable products)
79
	 * @param array $hiddenAttributeIds List of attribute IDs that should be stored along with the product in the order
80
	 * @param array $customAttributeValues Associative list of attribute IDs and arbitrary values that should be stored
81
	 * 	along with the product in the order
82
	 * @param string $warehouse Unique code of the warehouse to deliver the products from
83
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the product isn't available
84
	 */
85
	public function addProduct( $prodid, $quantity = 1, array $options = array(), array $variantAttributeIds = array(),
86
		array $configAttributeIds = array(), array $hiddenAttributeIds = array(), array $customAttributeValues = array(),
87
		$warehouse = 'default' )
88
	{
89
		$context = $this->getContext();
90
91
		$productItem = $this->getDomainItem( 'product', 'product.id', $prodid, array( 'media', 'supplier', 'price', 'product', 'text' ) );
92
93
		$orderBaseProductItem = \Aimeos\MShop\Factory::createManager( $context, 'order/base/product' )->createItem();
94
		$orderBaseProductItem->copyFrom( $productItem );
95
		$orderBaseProductItem->setQuantity( $quantity );
96
		$orderBaseProductItem->setWarehouseCode( $warehouse );
97
98
		$attr = array();
99
		$prices = $productItem->getRefItems( 'price', 'default', 'default' );
100
101
		switch( $productItem->getType() )
102
		{
103
			case 'select':
104
				$attr = $this->getVariantDetails( $orderBaseProductItem, $productItem, $prices, $variantAttributeIds, $options );
105
				break;
106
			case 'bundle':
107
				$this->addBundleProducts( $orderBaseProductItem, $productItem, $variantAttributeIds, $warehouse );
108
				break;
109
		}
110
111
		$priceManager = \Aimeos\MShop\Factory::createManager( $context, 'price' );
112
		$price = $priceManager->getLowestPrice( $prices, $quantity );
113
114
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $configAttributeIds, 'config' ) );
115
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, $hiddenAttributeIds, 'hidden' ) );
116
		$attr = array_merge( $attr, $this->createOrderProductAttributes( $price, $prodid, $quantity, array_keys( $customAttributeValues ), 'custom', $customAttributeValues ) );
117
118
		// remove product rebate of original price in favor to rebates granted for the order
119
		$price->setRebate( '0.00' );
120
121
		$orderBaseProductItem->setPrice( $price );
122
		$orderBaseProductItem->setAttributes( $attr );
123
124
		$this->basket->addProduct( $orderBaseProductItem );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method addProduct() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
125
		$this->domainManager->setSession( $this->basket );
126
	}
127
128
129
	/**
130
	 * Deletes a product item from the basket.
131
	 *
132
	 * @param integer $position Position number (key) of the order product item
133
	 */
134
	public function deleteProduct( $position )
135
	{
136
		$product = $this->basket->getProduct( $position );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method getProduct() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
137
138
		if( $product->getFlags() === \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
139
		{
140
			$msg = sprintf( 'Basket item at position "%1$d" cannot be deleted manually', $position );
141
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
142
		}
143
144
		$this->basket->deleteProduct( $position );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method deleteProduct() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
145
		$this->domainManager->setSession( $this->basket );
146
	}
147
148
149
	/**
150
	 * Edits the quantity of a product item in the basket.
151
	 *
152
	 * @param integer $position Position number (key) of the order product item
153
	 * @param integer $quantity New quantiy of the product item
154
	 * @param array $options Possible options are: 'stock'=>true|false
155
	 * 	The 'stock'=>false option allows adding products without being in stock.
156
	 * @param string[] $configAttributeCodes Codes of the product config attributes that should be REMOVED
157
	 */
158
	public function editProduct( $position, $quantity, array $options = array(),
159
		array $configAttributeCodes = array() )
160
	{
161
		$product = $this->basket->getProduct( $position );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method getProduct() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
162
		$product->setQuantity( $quantity ); // Enforce check immediately
163
164
		if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE )
165
		{
166
			$msg = sprintf( 'Basket item at position "%1$d" cannot be changed', $position );
167
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
168
		}
169
170
		$attributes = $product->getAttributes();
171
		foreach( $attributes as $key => $attribute )
172
		{
173
			if( in_array( $attribute->getCode(), $configAttributeCodes ) ) {
174
				unset( $attributes[$key] );
175
			}
176
		}
177
		$product->setAttributes( $attributes );
178
179
		$productItem = $this->getDomainItem( 'product', 'product.code', $product->getProductCode(), array( 'price', 'text' ) );
180
		$prices = $productItem->getRefItems( 'price', 'default' );
181
182
		$product->setPrice( $this->calcPrice( $product, $prices, $quantity ) );
183
184
		$this->basket->deleteProduct( $position );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method deleteProduct() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
185
		$this->basket->addProduct( $product, $position );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method addProduct() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
186
187
		$this->domainManager->setSession( $this->basket );
188
	}
189
190
191
	/**
192
	 * Adds the given coupon code and updates the basket.
193
	 *
194
	 * @param string $code Coupon code entered by the user
195
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid or not allowed
196
	 */
197
	public function addCoupon( $code )
198
	{
199
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
200
		$codeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon/code' );
201
202
203
		$search = $codeManager->createSearch( true );
204
		$expr = array(
205
			$search->compare( '==', 'coupon.code.code', $code ),
206
			$search->getConditions(),
207
		);
208
		$search->setConditions( $search->combine( '&&', $expr ) );
209
		$search->setSlice( 0, 1 );
210
211
		$result = $codeManager->searchItems( $search );
212
213
		if( ( $codeItem = reset( $result ) ) === false ) {
214
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid or not available any more', $code ) );
215
		}
216
217
218
		$search = $manager->createSearch( true );
219
		$expr = array(
220
			$search->compare( '==', 'coupon.id', $codeItem->getParentId() ),
221
			$search->getConditions(),
222
		);
223
		$search->setConditions( $search->combine( '&&', $expr ) );
224
		$search->setSlice( 0, 1 );
225
226
		$result = $manager->searchItems( $search );
227
228
		if( ( $item = reset( $result ) ) === false ) {
229
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon for code "%1$s" is not available any more', $code ) );
230
		}
231
232
233
		$provider = $manager->getProvider( $item, $code );
234
235
		if( $provider->isAvailable( $this->basket ) !== true ) {
236
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Requirements for coupon code "%1$s" aren\'t met', $code ) );
237
		}
238
239
		$provider->addCoupon( $this->basket );
240
		$this->domainManager->setSession( $this->basket );
241
	}
242
243
244
	/**
245
	 * Removes the given coupon code and its effects from the basket.
246
	 *
247
	 * @param string $code Coupon code entered by the user
248
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception if the coupon code is invalid
249
	 */
250
	public function deleteCoupon( $code )
251
	{
252
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'coupon' );
253
254
		$search = $manager->createSearch();
255
		$search->setConditions( $search->compare( '==', 'coupon.code.code', $code ) );
256
		$search->setSlice( 0, 1 );
257
258
		$result = $manager->searchItems( $search );
259
260
		if( ( $item = reset( $result ) ) === false ) {
261
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Coupon code "%1$s" is invalid', $code ) );
262
		}
263
264
		$manager->getProvider( $item, $code )->deleteCoupon( $this->basket );
265
		$this->domainManager->setSession( $this->basket );
266
	}
267
268
269
	/**
270
	 * Sets the address of the customer in the basket.
271
	 *
272
	 * @param string $type Address type constant from \Aimeos\MShop\Order\Item\Base\Address\Base
273
	 * @param \Aimeos\MShop\Common\Item\Address\Iface|array|null $value Address object or array with key/value pairs of address or null to remove address from basket
274
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If the billing or delivery address is not of any required type of
275
	 * 	if one of the keys is invalid when using an array with key/value pairs
276
	 */
277
	public function setAddress( $type, $value )
278
	{
279
		$address = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/address' )->createItem();
280
		$address->setType( $type );
281
282
		if( $value instanceof \Aimeos\MShop\Common\Item\Address\Iface )
283
		{
284
			$address->copyFrom( $value );
285
			$this->basket->setAddress( $address, $type );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method setAddress() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
286
		}
287
		else if( is_array( $value ) )
288
		{
289
			$this->setAddressFromArray( $address, $value );
290
			$this->basket->setAddress( $address, $type );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method setAddress() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
291
		}
292
		else if( $value === null )
293
		{
294
			$this->basket->deleteAddress( $type );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method deleteAddress() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
295
		}
296
		else
297
		{
298
			throw new \Aimeos\Controller\Frontend\Basket\Exception( sprintf( 'Invalid value for address type "%1$s"', $type ) );
299
		}
300
301
		$this->domainManager->setSession( $this->basket );
302
	}
303
304
305
	/**
306
	 * Sets the delivery/payment service item based on the service ID.
307
	 *
308
	 * @param string $type Service type code like 'payment' or 'delivery'
309
	 * @param string $id Unique ID of the service item
310
	 * @param array $attributes Associative list of key/value pairs containing the attributes selected or
311
	 * 	entered by the customer when choosing one of the delivery or payment options
312
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If there is no price to the service item attached
313
	 */
314
	public function setService( $type, $id, array $attributes = array() )
315
	{
316
		$context = $this->getContext();
317
318
		$serviceManager = \Aimeos\MShop\Factory::createManager( $context, 'service' );
319
		$serviceItem = $this->getDomainItem( 'service', 'service.id', $id, array( 'media', 'price', 'text' ) );
320
321
		$provider = $serviceManager->getProvider( $serviceItem );
322
		$result = $provider->checkConfigFE( $attributes );
323
		$unknown = array_diff_key( $attributes, $result );
324
325
		if( count( $unknown ) > 0 )
326
		{
327
			$msg = sprintf( 'Unknown attributes "%1$s"', implode( '","', array_keys( $unknown ) ) );
328
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
329
		}
330
331
		foreach( $result as $key => $value )
332
		{
333
			if( $value !== null ) {
334
				throw new \Aimeos\Controller\Frontend\Basket\Exception( $value );
335
			}
336
		}
337
338
		$orderBaseServiceManager = \Aimeos\MShop\Factory::createManager( $context, 'order/base/service' );
339
		$orderServiceItem = $orderBaseServiceManager->createItem();
340
		$orderServiceItem->copyFrom( $serviceItem );
341
342
		$price = $provider->calcPrice( $this->basket );
343
		// remove service rebate of original price
344
		$price->setRebate( '0.00' );
345
		$orderServiceItem->setPrice( $price );
346
347
		$provider->setConfigFE( $orderServiceItem, $attributes );
348
349
		$this->basket->setService( $orderServiceItem, $type );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method setService() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
350
		$this->domainManager->setSession( $this->basket );
351
	}
352
353
354
	/**
355
	 * Adds the bundled products to the order product item.
356
	 *
357
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Order product item
358
	 * @param \Aimeos\MShop\Product\Item\Iface $productItem Bundle product item
359
	 * @param array $variantAttributeIds List of product variant attribute IDs
360
	 * @param string $warehouse
361
	 */
362
	protected function addBundleProducts( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
363
		\Aimeos\MShop\Product\Item\Iface $productItem, array $variantAttributeIds, $warehouse )
364
	{
365
		$quantity = $orderBaseProductItem->getQuantity();
366
		$products = $subProductIds = $orderProducts = array();
367
		$orderProductManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product' );
368
369
		foreach( $productItem->getRefItems( 'product', null, 'default' ) as $item ) {
370
			$subProductIds[] = $item->getId();
371
		}
372
373
		if( count( $subProductIds ) > 0 )
374
		{
375
			$productManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' );
376
377
			$search = $productManager->createSearch( true );
378
			$expr = array(
379
				$search->compare( '==', 'product.id', $subProductIds ),
380
				$search->getConditions(),
381
			);
382
			$search->setConditions( $search->combine( '&&', $expr ) );
383
384
			$products = $productManager->searchItems( $search, array( 'attribute', 'media', 'price', 'text' ) );
385
		}
386
387
		foreach( $products as $product )
388
		{
389
			$prices = $product->getRefItems( 'price', 'default', 'default' );
390
391
			$orderProduct = $orderProductManager->createItem();
392
			$orderProduct->copyFrom( $product );
393
			$orderProduct->setWarehouseCode( $warehouse );
394
			$orderProduct->setPrice( $this->calcPrice( $orderProduct, $prices, $quantity ) );
395
396
			$orderProducts[] = $orderProduct;
397
		}
398
399
		$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...
400
	}
401
402
403
	/**
404
	 * Checks for a locale mismatch and migrates the products to the new basket if necessary.
405
	 */
406
	protected function checkLocale()
407
	{
408
		$errors = array();
409
		$context = $this->getContext();
410
		$session = $context->getSession();
411
		$locale = $this->basket->getLocale();
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Item\Iface as the method getLocale() does only exist in the following implementations of said interface: Aimeos\MShop\Order\Item\Base\Base, Aimeos\MShop\Order\Item\Base\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...
412
413
		$localeStr = $session->get( 'aimeos/basket/locale' );
414
		$localeKey = $locale->getSite()->getCode() . '|' . $locale->getLanguageId() . '|' . $locale->getCurrencyId();
415
416
		if( $localeStr !== null && $localeStr !== $localeKey )
417
		{
418
			$locParts = explode( '|', $localeStr );
419
			$locSite = ( isset( $locParts[0] ) ? $locParts[0] : '' );
420
			$locLanguage = ( isset( $locParts[1] ) ? $locParts[1] : '' );
421
			$locCurrency = ( isset( $locParts[2] ) ? $locParts[2] : '' );
422
423
			$localeManager = \Aimeos\MShop\Factory::createManager( $context, 'locale' );
424
			$locale = $localeManager->bootstrap( $locSite, $locLanguage, $locCurrency, false );
425
426
			$context = clone $context;
427
			$context->setLocale( $locale );
428
429
			$manager = \Aimeos\MShop\Order\Manager\Factory::createManager( $context )->getSubManager( 'base' );
430
			$basket = $manager->getSession();
431
432
			$this->copyAddresses( $basket, $errors, $localeKey );
433
			$this->copyServices( $basket, $errors );
434
			$this->copyProducts( $basket, $errors, $localeKey );
435
			$this->copyCoupons( $basket, $errors, $localeKey );
436
437
			$manager->setSession( $basket );
438
		}
439
440
		$session->set( 'aimeos/basket/locale', $localeKey );
441
	}
442
443
444
	/**
445
	 * Migrates the addresses from the old basket to the current one.
446
	 *
447
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
448
	 * @param array $errors Associative list of previous errors
449
	 * @param string $localeKey Unique identifier of the site, language and currency
450
	 * @return array Associative list of errors occured
451
	 */
452
	protected function copyAddresses( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
453
	{
454
		foreach( $basket->getAddresses() as $type => $item )
455
		{
456
			try
457
			{
458
				$this->setAddress( $type, $item->toArray() );
459
				$basket->deleteAddress( $type );
460
			}
461
			catch( \Exception $e )
462
			{
463
				$logger = $this->getContext()->getLogger();
464
				$str = 'Error migrating address with type "%1$s" in basket to locale "%2$s": %3$s';
465
				$logger->log( sprintf( $str, $type, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
466
				$errors['address'][$type] = $e->getMessage();
467
			}
468
		}
469
470
		return $errors;
471
	}
472
473
474
	/**
475
	 * Migrates the coupons from the old basket to the current one.
476
	 *
477
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
478
	 * @param array $errors Associative list of previous errors
479
	 * @param string $localeKey Unique identifier of the site, language and currency
480
	 * @return array Associative list of errors occured
481
	 */
482
	protected function copyCoupons( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
483
	{
484
		foreach( $basket->getCoupons() as $code => $list )
485
		{
486
			try
487
			{
488
				$this->addCoupon( $code );
489
				$basket->deleteCoupon( $code, true );
490
			}
491
			catch( \Exception $e )
492
			{
493
				$logger = $this->getContext()->getLogger();
494
				$str = 'Error migrating coupon with code "%1$s" in basket to locale "%2$s": %3$s';
495
				$logger->log( sprintf( $str, $code, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
496
				$errors['coupon'][$code] = $e->getMessage();
497
			}
498
		}
499
500
		return $errors;
501
	}
502
503
504
	/**
505
	 * Migrates the products from the old basket to the current one.
506
	 *
507
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
508
	 * @param array $errors Associative list of previous errors
509
	 * @param string $localeKey Unique identifier of the site, language and currency
510
	 * @return array Associative list of errors occured
511
	 */
512
	protected function copyProducts( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors, $localeKey )
513
	{
514
		foreach( $basket->getProducts() as $pos => $product )
515
		{
516
			if( $product->getFlags() & \Aimeos\MShop\Order\Item\Base\Product\Base::FLAG_IMMUTABLE ) {
517
				continue;
518
			}
519
520
			try
521
			{
522
				$attrIds = array();
523
524
				foreach( $product->getAttributes() as $attrItem ) {
525
					$attrIds[$attrItem->getType()][] = $attrItem->getAttributeId();
526
				}
527
528
				$this->addProduct(
529
					$product->getProductId(),
530
					$product->getQuantity(),
531
					array(),
532
					$this->getValue( $attrIds, 'variant', array() ),
533
					$this->getValue( $attrIds, 'config', array() ),
534
					$this->getValue( $attrIds, 'hidden', array() ),
535
					$this->getValue( $attrIds, 'custom', array() ),
536
					$product->getWarehouseCode()
537
				);
538
539
				$basket->deleteProduct( $pos );
540
			}
541
			catch( \Exception $e )
542
			{
543
				$code = $product->getProductCode();
544
				$logger = $this->getContext()->getLogger();
545
				$str = 'Error migrating product with code "%1$s" in basket to locale "%2$s": %3$s';
546
				$logger->log( sprintf( $str, $code, $localeKey, $e->getMessage() ), \Aimeos\MW\Logger\Base::INFO );
547
				$errors['product'][$pos] = $e->getMessage();
548
			}
549
		}
550
551
		return $errors;
552
	}
553
554
555
	/**
556
	 * Migrates the services from the old basket to the current one.
557
	 *
558
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
559
	 * @param array $errors Associative list of previous errors
560
	 * @return array Associative list of errors occured
561
	 */
562
	protected function copyServices( \Aimeos\MShop\Order\Item\Base\Iface $basket, array $errors )
563
	{
564
		foreach( $basket->getServices() as $type => $item )
565
		{
566
			try
567
			{
568
				$attributes = array();
569
570
				foreach( $item->getAttributes() as $attrItem ) {
571
					$attributes[$attrItem->getCode()] = $attrItem->getValue();
572
				}
573
574
				$this->setService( $type, $item->getServiceId(), $attributes );
575
				$basket->deleteService( $type );
576
			}
577
			catch( \Exception $e ) {; } // Don't notify the user as appropriate services can be added automatically
578
		}
579
580
		return $errors;
581
	}
582
583
584
	/**
585
	 * Fills the order address object with the values from the array.
586
	 *
587
	 * @param \Aimeos\MShop\Order\Item\Base\Address\Iface $address Address item to store the values into
588
	 * @param array $map Associative array of key/value pairs. The keys must be the same as when calling toArray() from
589
	 * 	an address item.
590
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception
591
	 */
592
	protected function setAddressFromArray( \Aimeos\MShop\Order\Item\Base\Address\Iface $address, array $map )
593
	{
594
		foreach( $map as $key => $value ) {
595
			$map[$key] = strip_tags( $value ); // prevent XSS
596
		}
597
598
		$errors = $address->fromArray( $map );
599
600
		if( count( $errors ) > 0 )
601
		{
602
			$msg = sprintf( 'Invalid address properties, please check your input' );
603
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg, 0, null, $errors );
604
		}
605
	}
606
607
608
	/**
609
	 * Returns the variant attributes and updates the price list if necessary.
610
	 *
611
	 * @param \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem Order product item
612
	 * @param \Aimeos\MShop\Product\Item\Iface &$productItem Product item which is replaced if necessary
613
	 * @param array &$prices List of product prices that will be updated if necessary
614
	 * @param array $variantAttributeIds List of product variant attribute IDs
615
	 * @param array $options Associative list of options
616
	 * @return \Aimeos\MShop\Order\Item\Base\Product\Attribute\Iface[] List of order product attributes
617
	 * @throws \Aimeos\Controller\Frontend\Basket\Exception If no product variant is found
618
	 */
619
	protected function getVariantDetails( \Aimeos\MShop\Order\Item\Base\Product\Iface $orderBaseProductItem,
620
		\Aimeos\MShop\Product\Item\Iface &$productItem, array &$prices, array $variantAttributeIds, array $options )
621
	{
622
		$attr = array();
623
		$productItems = $this->getProductVariants( $productItem, $variantAttributeIds );
624
625
		if( count( $productItems ) > 1 )
626
		{
627
			$msg = sprintf( 'No unique article found for selected attributes and product ID "%1$s"', $productItem->getId() );
628
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
629
		}
630
		else if( ( $result = reset( $productItems ) ) !== false ) // count == 1
631
		{
632
			$productItem = $result;
633
			$orderBaseProductItem->setProductCode( $productItem->getCode() );
634
635
			$subprices = $productItem->getRefItems( 'price', 'default', 'default' );
636
637
			if( count( $subprices ) > 0 ) {
638
				$prices = $subprices;
639
			}
640
641
			$orderProductAttrManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'order/base/product/attribute' );
642
			$variantAttributes = $productItem->getRefItems( 'attribute', null, 'variant' );
643
644
			foreach( $this->getAttributes( array_keys( $variantAttributes ), array( 'text' ) ) as $attrItem )
645
			{
646
				$orderAttributeItem = $orderProductAttrManager->createItem();
647
				$orderAttributeItem->copyFrom( $attrItem );
648
				$orderAttributeItem->setType( 'variant' );
649
650
				$attr[] = $orderAttributeItem;
651
			}
652
		}
653
		else if( !isset( $options['variant'] ) || $options['variant'] != false ) // count == 0
654
		{
655
			$msg = sprintf( 'No article found for selected attributes and product ID "%1$s"', $productItem->getId() );
656
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
657
		}
658
659
		return $attr;
660
	}
661
}
662