Completed
Push — master ( 65613c...8f8233 )
by Lawrence
02:16
created

Prestashop   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 305
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 26
Bugs 13 Features 0
Metric Value
wmc 44
c 26
b 13
f 0
lcom 1
cbo 6
dl 0
loc 305
rs 8.3396

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A onOpen() 0 7 1
C onMessage() 0 22 7
B getCombinationData() 0 18 5
A getAttribute() 0 16 2
B buildAttributes() 0 23 6
A getFinalPrice() 0 6 2
A getAvailableDate() 0 6 2
A getCombinationSpecificPrice() 0 12 2
A getCombination() 0 19 4
A getAttributeBase() 0 14 1
A getStockQuantity() 0 7 1
A getAttributePricing() 0 9 1
A getSpecificPrice() 0 14 2
A getSpecificPriceData() 0 17 1
A getNumberSpecificPrice() 0 11 2
A getComboImage() 0 12 2
A onClose() 0 6 1
A onError() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Prestashop often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Prestashop, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace ABM\Wasabi;
4
5
use Analog\Analog;
6
use Ratchet\ConnectionInterface;
7
use Ratchet\MessageComponentInterface;
8
9
class Prestashop implements MessageComponentInterface
10
{
11
    protected $clients;
12
    protected $dbConn;
13
14
    public function __construct()
15
    {
16
        $this->clients = new \SplObjectStorage();
17
        // Now connect to PS DB using Simplon on Composer
18
        $this->dbConn = new \Simplon\Mysql\Mysql(
19
    '127.0.0.1',
20
    _DB_USER_,
21
    _DB_PASSWD_,
22
    _DB_NAME_
23
        );
24
        $log_file = 'wasabi.log';
25
        Analog::handler(\Analog\Handler\File::init($log_file));
0 ignored issues
show
Documentation introduced by
\Analog\Handler\File::init($log_file) is of type object<Closure>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
26
    }
27
28
    public function onOpen(ConnectionInterface $conn)
29
    {
30
        // Store the new connection to send messages to later
31
        $this->clients->attach($conn);
32
33
        Analog::log("New connection: $conn->resourceId");
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
34
    }
35
36
    public function onMessage(ConnectionInterface $from, $msg)
37
    {
38
        $result = null;
39
        foreach ($this->clients as $client) {
40
            if ($from == $client) {
41
                $type = strtolower(substr($msg, 0, strpos($msg, '|')));
42
                $data = substr($msg, strpos($msg, '|') + 1);
43
                switch ($type) {
44
                    case 'cart': $result = Cart::getCartData($data);
0 ignored issues
show
Bug introduced by
The method getCartData() cannot be called from this context as it is declared private in class ABM\Wasabi\Cart.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
45
                                    break;
46
                    case 'prod': $result = Product::getProductData($data);
0 ignored issues
show
Bug introduced by
The method getProductData() cannot be called from this context as it is declared private in class ABM\Wasabi\Product.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
47
                                    break;
48
                    case 'comb': $result = $this->getCombinationData($data);
49
                                    break;
50
                    default:        break;
51
                }
52
                if (!empty($result)) {
53
                    $client->send(json_encode($result));
54
                }
55
            }
56
        }
57
    }
58
59
    /**
60
     * @param string $data
61
     */
62
    private function getCombinationData($data)
63
    {
64
        $product = substr($data, 0, strpos($data, ','));
65
        $combinations = [];
66
        $vars = explode(',', $data);
67
        Analog::log("Product variables: $data");
68
        $choices = array_slice($vars, 1);
69
        $id_product_attribute = $this->getAttribute($product, $choices);
70
        Analog::log("Product combination: $id_product_attribute");
71
        $combo_groups = $this->getCombination($product, $id_product_attribute);
72
        if (!empty($combo_groups) && is_array($combo_groups) && $combo_groups) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $combo_groups of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
73
            foreach ($combo_groups as $k => $row) {
74
                $combinations = $this->buildAttributes($combinations, $id_product_attribute, $row);
75
            }
76
        }
77
78
        return $combinations;
79
    }
80
81
  
82
    /**
83
     * @param string $product
84
     */
85
    private function getAttribute($product, $choices)
86
    {
87
        $sql = 'SELECT pac.id_product_attribute from '._DB_PREFIX_.'product_attribute_combination as pac
88
                LEFT JOIN  '._DB_PREFIX_.'product_attribute as ppa on ppa.id_product_attribute = pac.id_product_attribute
89
                LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute)
90
                WHERE ppa.id_product = '.$product;
91
        foreach ($choices as $value) {
92
            $sql .= ' AND pac.id_product_attribute IN
93
                    ( SELECT pac.id_product_attribute from '._DB_PREFIX_.'product_attribute_combination as pac
94
                    WHERE pac.id_attribute = '.(int) $value.')  ';
95
        }
96
        $sql .= ' GROUP BY pac.id_product_attribute';
97
        $id_product_attribute = $this->dbConn->fetchColumn($sql);
98
99
        return $id_product_attribute;
100
    }
101
102
    /**
103
     * @param null|string $id_product_attribute
104
     */
105
    private function buildAttributes($combinations, $id_product_attribute, $row)
106
    {
107
        $combinationSet = [];
108
        $specific_price = null;
109
110
        $combinations['name'] = $row['product_name'];
111
        $typcheck = ['id_product', 'price', 'base_price', 'price', 'ecotax', 'weight', 'quantity', 'unit_impact', 'minimal_quantity'];
112
113
        foreach ($typcheck as $key => $value) {
114
            ((strpos($value, 'price') !== false) || (strpos($value, 'weight') !== false) || (strpos($value, 'ecotax') !== false) || (strpos($value, 'impact') !== false)) ? $combinations[$value] = (float) $row[$value] : $combinations[$value] = (int) $row[$value];
115
        }
116
        $combinations['attributes_values'][$row['id_attribute_group']] = $row['attribute_name'];
117
        $combinations['attributes'][] = (int) $row['id_attribute'];
118
119
        list($combinationSet[(int) $row['id_product_attribute']], $combinations['specific_price']) = $this->getCombinationSpecificPrice($combinationSet, $row, $id_product_attribute);
120
121
        $combinations['reference'] = $row['reference'];
122
        $combinations['available_date'] = $this->getAvailableDate($row);
123
        $combinations['image'] = $this->getComboImage($id_product_attribute);
124
        $combinations['final_price'] = $this->getFinalPrice($row, $specific_price);
125
126
        return $combinations;
127
    }
128
129
    private function getFinalPrice($row, $specific_price)
130
    {
131
        $specific_price['price'] != 0 ? $final_price = (((float) $row['base_price'] + (float) $row['price']) * (((int) 100 - $specific_price['reduction_percent']) / 100)) : $final_price = (float) $row['base_price'] + (float) $row['price'];
132
133
        return $final_price;
134
    }
135
136
    private function getAvailableDate($row)
137
    {
138
        ($row['available_date'] != '0000-00-00') ? $dater = $row['available_date'] : $dater = '';
139
140
        return $dater;
141
    }
142
143
    /**
144
     * @param null|string $id_product_attribute
145
     */
146
    private function getCombinationSpecificPrice($combinationSet, $row, $id_product_attribute)
147
    {
148
        // Call getSpecificPrice in order to set $combination_specific_price
149
                if (!isset($combinationSet[(int) $row['id_product_attribute']])) {
150
                    $specific_price = $this->getSpecificPrice($id_product_attribute, $row['id_product']);
151
                    $combinationSet[(int) $row['id_product_attribute']] = true;
152
153
                    return [$combinationSet, $specific_price];
154
                } else {
155
                    return [false, null];
156
                }
157
    }
158
159
    /**
160
     * @param null|string $id_product_attribute
161
     * @param string      $product
162
     */
163
    private function getCombination($product, $id_product_attribute)
164
    {
165
        $combo = $this->getAttributeBase($id_product_attribute);
166
167
        if (is_array($combo)) {
168
            foreach ($combo as $key => $value) {
169
                $combo['base_price'] = (float) Product::getOrderPrice($product);
0 ignored issues
show
Bug introduced by
The method getOrderPrice() cannot be called from this context as it is declared private in class ABM\Wasabi\Product.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
170
                $combo['quantity'] = (int) $this->getStockQuantity($product, $id_product_attribute);
171
                $combo['id_product'] = (int) $product;
172
                $combo['product_name'] = (int) Product::getProductName($product);
0 ignored issues
show
Bug introduced by
The method getProductName() cannot be called from this context as it is declared private in class ABM\Wasabi\Product.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
173
                $pricing = $this->getAttributePricing($id_product_attribute);
174
                foreach ($pricing as $ki => $val) {
0 ignored issues
show
Bug introduced by
The expression $pricing of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
175
                    $combo[$ki] = $val;
176
                }
177
            }
178
        }
179
180
        return $combo;
181
    }
182
183
    /**
184
     * @param null|string $attribute
185
     */
186
    private function getAttributeBase($attribute)
187
    {
188
        $sql = 'SELECT ag.id_attribute_group, ag.is_color_group, agl.name AS group_name, agl.public_name AS public_group_name,
189
                    a.id_attribute, al.name AS attribute_name, a.color AS attribute_color, ag.group_type, pac.id_product_attribute
190
            FROM '._DB_PREFIX_.'product_attribute_combination pac
191
            LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute)
192
            LEFT JOIN  '._DB_PREFIX_.'attribute_group ag ON (ag.id_attribute_group = a.id_attribute_group)
193
            LEFT JOIN '._DB_PREFIX_.'attribute_lang al ON (a.id_attribute = al.id_attribute)
194
            LEFT JOIN '._DB_PREFIX_.'attribute_group_lang agl ON (ag.id_attribute_group = agl.id_attribute_group)
195
            WHERE pac.id_product_attribute='.(int) $attribute;
196
        $result = $this->dbConn->fetchRowMany($sql);
197
198
        return $result;
199
    }
200
201
    /**
202
     * @param null|string $attribute
203
     * @param string      $product
204
     */
205
    private function getStockQuantity($product, $attribute)
206
    {
207
        $sql = 'SELECT stock.quantity from '._DB_PREFIX_.'stock_available as stock WHERE stock.id_product = '.(int) $product.'AND stock.id_product_attribute = '.(int) $attribute;
208
        $result = $this->dbConn->fetchColumn($sql);
209
210
        return $result;
211
    }
212
213
    /**
214
     * @param null|string $attribute
215
     */
216
    private function getAttributePricing($attribute)
217
    {
218
        $sql = 'SELECT pas.price, pas.ecotax, pas.weight, pas.default_on, pa.reference, pas.unit_price_impact, 
219
                pas.minimal_quantity, pas.available_date FROM '._DB_PREFIX_.'product_attribute_shop pas 
220
                WHERE pas.id_product_attribute = '.(int) $attribute;
221
        $result = $this->dbConn->fetchRowMany($sql);
222
223
        return $result;
224
    }
225
226
    /**
227
     * @param null|string $id_product_attribute
228
     * @param string      $id_product
229
     */
230
    private function getSpecificPrice($id_product_attribute, $id_product)
231
    {
232
        $specific_price = [];
233
        if ($this->getNumberSpecificPrice($id_product_attribute, $id_product) > 0) {
234
            $result = $this->getSpecificPriceData($id_product_attribute, $id_product, date('Y-m-d H:i:s'));
235
            $specific_price['price'] = $result['price'];
236
            $specific_price['id_product_attribute'] = $result['id_product_attribute'];
237
            $specific_price['reduction_percent'] = (int) 100 * $result['reduction'];
238
            $specific_price['reduction_price'] = 0;
239
            $specific_price['reduction_type'] = $result['reduction_type'];
240
241
            return $specific_price;
242
        }
243
    }
244
245
    /**
246
     * @param string      $now
247
     * @param null|string $id_product_attribute
248
     * @param string      $id_product
249
     */
250
    private function getSpecificPriceData($id_product_attribute, $id_product, $now)
251
    {
252
        $sql = 'SELECT * FROM '._DB_PREFIX_.'specific_price
253
                            WWHERE id_product = '.(int) $id_product.'
254
                            AND id_product_attribute IN (0, '.(int) $id_product_attribute.')
255
                            AND (
256
                                   (from = \'0000-00-00 00:00:00\' OR \''.$now.'\' >= from)
257
                            AND
258
                                     (to = \'0000-00-00 00:00:00\' OR \''.$now.'\' <= to)
259
                            ) ';
260
261
        $sql .= ' ORDER BY id_product_attribute DESC, from_quantity DESC, id_specific_price_rule ASC';
262
263
        $result = $this->dbConn->fetchRow($sql);
264
265
        return $result;
266
    }
267
268
    /**
269
     * @param null|string $id_product_attribute
270
     * @param string      $id_product
271
     */
272
    private function getNumberSpecificPrice($id_product_attribute, $id_product)
273
    {
274
        $sql = 'SELECT COUNT(*) FROM '._DB_PREFIX_.'specific_price WHERE id_product = '.(int) $id_product.' AND id_product_attribute = 0';
275
        $spec = $this->dbConn->fetchColumn($sql);
276
        if ($spec == 0) {
277
            $sql = 'SELECT COUNT(*) FROM '._DB_PREFIX_.'specific_price WHERE id_product_attribute = '.(int) $id_product_attribute;
278
            $spec = $this->dbConn->fetchColumn($sql);
279
        }
280
281
        return $spec;
282
    }
283
284
    /**
285
     * @param null|string $id_product_attribute
286
     */
287
    private function getComboImage($id_product_attribute)
288
    {
289
        $sql = 'SELECT pai.id_imageas image
290
                        FROM '._DB_PREFIX_.'product_attribute_image pai
291
                        LEFT JOIN '._DB_PREFIX_.'image_lang il ON (il.id_image = pai.id_image)
292
                        LEFT JOIN '._DB_PREFIX_.'image i ON (i.id_image = pai.id_image)
293
                        WHERE pai.id_product_attribute = '.(int) $id_product_attribute.' ORDER by i.position';
294
        $image = $this->dbConn->fetchColumn($sql);
295
        ($image !== false) ? $imager = (int) $image : $imager = -1;
296
297
        return $imager;
298
    }
299
300
    public function onClose(ConnectionInterface $conn)
301
    {
302
        // The connection is closed, remove it, as we can no longer send it messages
303
        $this->clients->detach($conn);
304
        Analog::log('Connection '.$conn->resourceId.' has disconnected');
0 ignored issues
show
Bug introduced by
Accessing resourceId on the interface Ratchet\ConnectionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
305
    }
306
307
    public function onError(ConnectionInterface $conn, \Exception $e)
308
    {
309
        echo "An error has occurred: {$e->getMessage()}\n";
310
        Analog::log('Error: '.$e->getMessage().'');
311
        $conn->close();
312
    }
313
}
314