Completed
Push — master ( 4ffab1...137f58 )
by Aimeos
07:59
created

Stock::addProduct()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 25
rs 8.439
cc 6
eloc 13
nc 16
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), 2016
6
 * @package Controller
7
 * @subpackage Frontend
8
 */
9
10
11
namespace Aimeos\Controller\Frontend\Basket\Decorator;
12
13
14
/**
15
 * Stock level check for basket controller
16
 *
17
 * @package Controller
18
 * @subpackage Frontend
19
 */
20
class Stock
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 categorized 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 $variantIds List of variant-building attribute IDs that identify a specific product
35
	 * 	in a selection products
36
	 * @param array $configIds  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 $hiddenIds List of attribute IDs that should be stored along with the product in the order
39
	 * @param array $customValues Associative list of attribute IDs and arbitrary values that should be stored
40
	 * 	along with the product in the order
41
	 * @param string $warehouse Unique code of the warehouse 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 $variantIds = array(),
45
		array $configIds = array(), array $hiddenIds = array(), array $customValues = array(), $warehouse = 'default' )
46
	{
47
		$stocklevel = null;
48
49
		if( !isset( $options['stock'] ) || $options['stock'] != false ) {
50
			$stocklevel = $this->getStockLevel( $prodid, $warehouse );
51
		}
52
53
		$qty = ( $stocklevel !== null ? min( $stocklevel, $quantity ) : $quantity );
54
55
		if( $qty > 0 )
56
		{
57
			$this->getController()->addProduct(
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\Controller\Frontend\Common\Iface as the method addProduct() does only exist in the following implementations of said interface: Aimeos\Controller\Frontend\Basket\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...
58
				$prodid, $qty, $options, $variantIds, $configIds, $hiddenIds, $customValues, $warehouse
59
			);
60
		}
61
62
		if( $qty < $quantity )
63
		{
64
			$product = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product' )->getItem( $prodid );
65
			$msg = sprintf( 'There are not enough products "%1$s" in stock', $product->getName() );
66
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
67
		}
68
	}
69
70
71
	/**
72
	 * Edits the quantity of a product item in the basket.
73
	 *
74
	 * @param integer $position Position number (key) of the order product item
75
	 * @param integer $quantity New quantiy of the product item
76
	 * @param array $options Possible options are: 'stock'=>true|false
77
	 * 	The 'stock'=>false option allows adding products without being in stock.
78
	 * @param string[] $configCodes Codes of the product config attributes that should be REMOVED
79
	 */
80
	public function editProduct( $position, $quantity, array $options = array(), array $configCodes = array() )
81
	{
82
		$stocklevel = null;
83
		$product = $this->getController()->get()->getProduct( $position );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\Controller\Frontend\Common\Iface as the method get() does only exist in the following implementations of said interface: Aimeos\Controller\Frontend\Basket\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...
84
85
		if( !isset( $options['stock'] ) || $options['stock'] != false ) {
86
			$stocklevel = $this->getStockLevel( $product->getProductId(), $product->getWarehouseCode() );
87
		}
88
89
		$qty = ( $stocklevel !== null ? min( $stocklevel, $quantity ) : $quantity );
90
91
		$this->getController()->editProduct( $position, $qty, $options, $configCodes );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\Controller\Frontend\Common\Iface as the method editProduct() does only exist in the following implementations of said interface: Aimeos\Controller\Frontend\Basket\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...
92
93
		if( $qty < $quantity )
94
		{
95
			$msg = sprintf( 'There are not enough products "%1$s" in stock', $product->getName() );
96
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
97
		}
98
	}
99
100
101
	/**
102
	 * Returns the highest stock level for the product.
103
	 *
104
	 * @param string $prodid Unique ID of the product
105
	 * @param string $warehouse Unique code of the warehouse
106
	 * @return integer|null Number of available items in stock (null for unlimited stock)
107
	 */
108
	protected function getStockLevel( $prodid, $warehouse )
109
	{
110
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'product/stock' );
111
112
		$search = $manager->createSearch( true );
113
		$expr = array(
114
			$search->compare( '==', 'product.stock.parentid', $prodid ),
115
			$search->getConditions(),
116
			$search->compare( '==', 'product.stock.warehouse.code', $warehouse ),
117
		);
118
		$search->setConditions( $search->combine( '&&', $expr ) );
119
120
		$result = $manager->searchItems( $search );
121
122
		if( empty( $result ) )
123
		{
124
			$msg = sprintf( 'No stock for product ID "%1$s" and warehouse "%2$s" available', $prodid, $warehouse );
125
			throw new \Aimeos\Controller\Frontend\Basket\Exception( $msg );
126
		}
127
128
		$stocklevel = null;
129
130
		foreach( $result as $item )
131
		{
132
			if( ( $stock = $item->getStockLevel() ) === null ) {
133
				return null;
134
			}
135
136
			$stocklevel = max( (int) $stocklevel, $item->getStockLevel() );
137
		}
138
139
		return $stocklevel;
140
	}
141
}
142