UomToCart   B
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 262
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 1 Features 1
Metric Value
wmc 38
c 3
b 1
f 1
lcom 2
cbo 8
dl 0
loc 262
ccs 0
cts 206
cp 0
rs 8.3999

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __invoke() 0 15 4
A uomToArray() 0 22 2
A getDisplayName() 0 12 4
A getFewVsMany() 0 4 1
A uomsForProduct() 0 7 2
A mergeEnabledUoms() 0 8 2
A buildersToUomsArray() 0 11 3
A renderUoms() 0 9 3
A renderOne() 0 10 1
A renderFew() 0 15 2
A displayPrice() 0 7 2
B renderMany() 0 33 3
A selectUomString() 0 8 3
B newForm() 0 40 2
A setFewVsMany() 0 5 1
A getService() 0 10 3
1
<?php
2
3
namespace SpeckCatalog\View\Helper;
4
5
use Zend\View\Helper\HelperInterface;
6
use Zend\View\Helper\AbstractHelper;
7
use Zend\View\Model\ViewModel;
8
use Zend\ServiceManager\ServiceLocatorAwareInterface;
9
use Zend\ServiceManager\ServiceLocatorAwareTrait;
10
use Zend\Form\Form;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, SpeckCatalog\View\Helper\Form.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
11
use SpeckCatalog\Model\ProductUom\Relational as ProductUom;
12
use SpeckCatalog\Model\Product\Relational as ProductModel;
13
14
class UomToCart extends AbstractHelper implements ServiceLocatorAwareInterface
15
{
16
    use ServiceLocatorAwareTrait;
17
18
    protected $services = array(
19
        'product_uom' => 'speckcatalog_product_uom_service',
20
        'product'     => 'speckcatalog_productservice',
21
        'builder'     => 'speckcatalog_builder_product_service',
22
    );
23
24
    protected $templates = array(
25
        'single' => '/speck-catalog/product/product-uom/single',
26
        'few'    => '/speck-catalog/product/product-uom/few',
27
        'many'   => '/speck-catalog/product/product-uom/many',
28
    );
29
30
    /**
31
     * how many uoms before we change the display style from few to many
32
     */
33
    protected $fewVsMany = 3;
34
35
    /**
36
     * whether or not to use "of 1" when the quantity of a uom is 1
37
     * example: 'Each of 1' or just 'Each'
38
     */
39
    protected $of1 = false;
40
41
    //product can be product model or productId
42
    public function __invoke($product, $builderProductId = null, $uomString = null, $quantity = 1)
43
    {
44
        if (!$product) {
45
            return;
46
        }
47
        if (is_numeric($product)) {
48
            $product = $this->getService('product')->find(array('product_id' => $product), array('builders', 'uoms'));
49
        }
50
        $uoms = $this->uomsForProduct($product);
51
        if ($builderProductId) {
52
            $productUoms = $this->getService('product_uom')->getByProductId($builderProductId);
53
            $uoms = $this->mergeEnabledUoms($productUoms, $uoms);
54
        }
55
        return $this->renderUoms($uoms, $uomString, $quantity);
56
    }
57
58
    public function uomsForProduct(ProductModel $product)
59
    {
60
        if ($product->getProductTypeId() == 1) {
61
            return $this->buildersToUomsArray($product->getBuilders());
62
        }
63
        return $this->mergeEnabledUoms($product->getUoms());
64
    }
65
66
    //merge some enabled uoms into an array of disabled uoms
67
    public function mergeEnabledUoms(array $enabled, array $uoms = array())
68
    {
69
        foreach ($enabled as $uom) {
70
            $uomArray = $this->uomToArray($uom, true);
71
            $uoms[$uomArray['uom_string']] = $uomArray;
72
        }
73
        return $uoms;
74
    }
75
76
    //returns array of "disabled" uoms
77
    public function buildersToUomsArray(array $builders)
78
    {
79
        $uoms = array();
80
        foreach ($builders as $builder) {
81
            foreach ($builder->getProduct()->getUoms() as $uom) {
82
                $uomArray = $this->uomToArray($uom);
83
                $uoms[$uomArray['uom_string']] = $uomArray;
84
            }
85
        }
86
        return $uoms;
87
    }
88
89
    public function uomToArray(ProductUom $uom, $enabled = false)
90
    {
91
        $this->getService('product_uom')->populate($uom);
92
        //data needed to represent a uom to be displayed
93
        $data = array(
94
            'enabled'    => false,
95
            'key'        => $uom->getUomCode().$uom->getQuantity(),
96
            'price'      => 'N/A',
97
            'name'       => $uom->getUom()->getName(),
98
            'code'       => $uom->getUomCode(),
99
            'quantity'   => $uom->getQuantity(),
100
            'uom_string' => $uom->getUomCode().$uom->getQuantity(),
101
        );
102
        if ($enabled) {
103
            $data['enabled'] = true;
104
            $data['key'] = $uom->getProductId()
105
                . ':' . $uom->getUomCode()
106
                . ':' . $uom->getQuantity();
107
            $data['price'] = $uom->getPrice();
108
        }
109
        return $data;
110
    }
111
112
    public function renderUoms(array $uoms, $uomString = null, $quantity = 1)
113
    {
114
        if (count($uoms) === 1) {
115
            return $this->renderOne(array_pop($uoms), $quantity);
116
        } elseif (count($uoms) <= $this->fewVsMany) {
117
            return $this->renderFew($uoms, $uomString, $quantity);
118
        }
119
        return $this->renderMany($uoms, $uomString, $quantity);
120
    }
121
122
    public function renderOne($uom, $quantity = 1)
123
    {
124
        $form = $this->newForm($uom, true, $quantity);
125
        $uom['display_name'] = $this->getDisplayName($uom, true);
126
127
        $view = new ViewModel(array('form' => $form, 'uom' => $uom));
128
        $view->setTerminal(true)->setTemplate($this->templates['single']);
129
130
        return $this->getView()->render($view);
131
    }
132
133
    public function renderFew(array $uoms, $uomString = null, $quantity = 1)
0 ignored issues
show
Unused Code introduced by
The parameter $uomString is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $quantity is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
134
    {
135
        $forms = array();
136
        foreach ($uoms as $i => $uom) {
137
            $uoms[$i]['display_name'] = $this->getDisplayName($uom, true);
138
            $child = $this->newForm($uom);
139
            $child->get('submit')->setName('uom')->setValue($uom['key']);
140
            $forms[$uom['uom_string']] = $child;
141
        }
142
143
        $view = new ViewModel(array('forms' => $forms, 'uoms' => $uoms));
144
        $view->setTerminal(true)->setTemplate($this->templates['few']);
145
146
        return $this->getView()->render($view);
147
    }
148
149
    public function getDisplayName($uom, $appendPrice = false)
150
    {
151
        if ($uom['quantity'] === 1 && $this->of1 === false) {
152
            $name = $uom['name'];
153
        } else {
154
            $name = $uom['name'] . ' of ' . $uom['quantity'];
155
        }
156
        if ($appendPrice) {
157
            $name .= ' - ' . $this->displayPrice($uom['price']);
158
        }
159
        return $name;
160
    }
161
162
    public function displayPrice($price)
163
    {
164
        if (is_numeric($price)) {
165
            return '$' . number_format($price, 2);
166
        }
167
        return $price;
168
    }
169
170
    public function renderMany($uoms, $uomString = null, $quantity = 1)
171
    {
172
        foreach ($uoms as $uom) {
173
            $key= $uom['key'];
174
            $options[$key] = array(
0 ignored issues
show
Coding Style Comprehensibility introduced by
$options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $options = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
175
                'value' => $key,
176
                'label' => $this->getDisplayName($uom, true),
177
            );
178
            if ($uom['enabled'] == false) {
179
                $options[$key]['attributes'] = array('disabled' => 'disabled');
180
            }
181
        }
182
183
        $form = $this->newForm(null, false, $quantity);
184
        $form->add(array(
185
            'name' => 'uom',
186
            'type' => 'Zend\Form\Element\Select',
187
            'attributes' => array(
188
                'type' => 'select',
189
                'options' => $options,
0 ignored issues
show
Bug introduced by
The variable $options does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
190
            ),
191
            'options' => array(
192
                'empty_option' => '',
193
                'label' => 'Unit Of Measure',
194
            ),
195
        ));
196
        $selectedUomString = $this->selectUomString($uoms, $uomString);
197
        $form->get('uom')->setValue($selectedUomString);
198
199
        $view = new ViewModel(array('form' => $form));
200
        $view->setTerminal(true)->setTemplate($this->templates['many']);
201
        return $this->getView()->render($view);
202
    }
203
204
    public function selectUomString(array $uoms = array(), $uomString = '')
205
    {
206
        foreach ($uoms as $uom) {
207
            if ($uomString === $uom['uom_string']) {
208
                return $uomString;
209
            }
210
        }
211
    }
212
213
    public function newForm($uom = null, $uomTextField = false, $quantity = 1)
214
    {
215
        $form = new Form($uom['uom_string']);
216
217
        $form->add(array(
218
            'name' => 'submit',
219
            'type' => 'Zend\Form\Element\Submit',
220
            'attributes' => array(
221
                'class' => 'btn add-to-cart',
222
            ),
223
            'options' => array(
224
                'label' => 'Add To Cart',
225
            ),
226
        ));
227
        $form->add(array(
228
            'name' => 'quantity',
229
            'attributes' => array(
230
                'type' => 'text',
231
                'value' => $quantity,
232
                'id' => 'quantity-to-cart',
233
            ),
234
            'options' => array(
235
                'label' => 'Quantity',
236
            ),
237
            'value' => $quantity,
238
        ));
239
        $form->get('quantity')->setValue($quantity);
240
241
        if ($uomTextField) {
242
            $form->add(array(
243
                'name' => 'uom',
244
                'attributes' => array(
245
                    'type' => 'hidden',
246
                ),
247
            ));
248
            $form->get('uom')->setValue($uom['key']);
249
        }
250
251
        return $form;
252
    }
253
254
    public function getFewVsMany()
255
    {
256
        return $this->fewVsMany;
257
    }
258
259
    public function setFewVsMany($fewVsMany)
260
    {
261
        $this->fewVsMany = $fewVsMany;
262
        return $this;
263
    }
264
265
    public function getService($name)
266
    {
267
        if (!array_key_exists($name, $this->services)) {
268
            throw new \Exception('invalid service name');
269
        }
270
        if (is_string($this->services[$name])) {
271
            $this->services[$name] = $this->getServiceLocator()->getServiceLocator()->get($this->services[$name]);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\ServiceManager\ServiceLocatorInterface as the method getServiceLocator() does only exist in the following implementations of said interface: Zend\Barcode\ObjectPluginManager, Zend\Barcode\RendererPluginManager, Zend\Cache\PatternPluginManager, Zend\Cache\Storage\AdapterPluginManager, Zend\Cache\Storage\PluginManager, Zend\Config\ReaderPluginManager, Zend\Config\WriterPluginManager, Zend\Crypt\SymmetricPluginManager, Zend\Crypt\Symmetric\PaddingPluginManager, Zend\Feed\Reader\ExtensionPluginManager, Zend\Feed\Writer\ExtensionPluginManager, Zend\File\Transfer\Adapter\FilterPluginManager, Zend\Filter\FilterPluginManager, Zend\Form\FormElementManager, Zend\Hydrator\HydratorPluginManager, Zend\I18n\Translator\LoaderPluginManager, Zend\InputFilter\InputFilterPluginManager, Zend\Log\ProcessorPluginManager, Zend\Log\WriterPluginManager, Zend\Log\Writer\FilterPluginManager, Zend\Log\Writer\FormatterPluginManager, Zend\Mvc\Controller\ControllerManager, Zend\Mvc\Controller\PluginManager, Zend\Mvc\Router\RoutePluginManager, Zend\Paginator\AdapterPluginManager, Zend\Paginator\ScrollingStylePluginManager, Zend\Permissions\Acl\Assertion\AssertionManager, Zend\Serializer\AdapterPluginManager, Zend\ServiceManager\AbstractPluginManager, Zend\Stdlib\Hydrator\HydratorPluginManager, Zend\Tag\Cloud\DecoratorPluginManager, Zend\Text\Table\DecoratorManager, Zend\Validator\ValidatorPluginManager, Zend\View\HelperPluginManager, Zend\View\Helper\Navigation\PluginManager.

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...
272
        }
273
        return $this->services[$name];
274
    }
275
}
276