Completed
Push — master ( 53f387...6a6ffb )
by James
22:06
created

Cart::restore()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 25
ccs 6
cts 6
cp 1
rs 8.439
cc 6
eloc 13
nc 4
nop 0
crap 6
1
<?php
2
3
namespace jamesdb\Cart;
4
5
use jamesdb\Cart\CartItem;
6
use jamesdb\Cart\Event as CartEvent;
7
use jamesdb\Cart\Exception as CartException;
8
use jamesdb\Cart\Storage\StorageInterface;
9
use League\Event\Emitter;
10
use SebastianBergmann\Money\Currency;
11
use SebastianBergmann\Money\Money;
12
13
class Cart implements CurrencyAwareInterface
14
{
15
    use CurrencyAwareTrait;
16
17
    /**
18
     * @var string
19
     */
20
    protected $identifier;
21
22
    /**
23
     * @var \jamesdb\Cart\Storage\StorageInterface
24
     */
25
    protected $storage;
26
27
    /**
28
     * Cart Contents.
29
     *
30
     * @var array
31
     */
32
    protected $contents = [];
33
34
    /**
35
     * Event Emitter.
36
     *
37
     * @var \League\Event\Emitter
38
     */
39
    protected $eventEmitter;
40
41
    /**
42
     * Constructor.
43
     *
44
     * @param string                                 $identifier
45
     * @param \jamesdb\Cart\Storage\StorageInterface $storage
46
     */
47
    public function __construct($identifier, StorageInterface $storage)
48
    {
49
        $this->identifier   = $identifier;
50
        $this->storage      = $storage;
51
        $this->currency     = $this->currency ?: new Currency('GBP');
52 72
        $this->eventEmitter = $this->eventEmitter ?: new Emitter();
53
54 72
        $this->restore();
55 72
    }
56 72
57 72
    /**
58
     * Add an Event Listener to the Emitter.
59 72
     *
60 72
     * @param  string          $eventName
61
     * @param  callable|object $listener
62
     *
63
     * @return void
64
     */
65
    public function addEventListener($eventName, $listener)
66
    {
67 3
        $this->eventEmitter->addListener($eventName, $listener);
68
    }
69 3
70 3
    /**
71
     * Returns the Event Emitter.
72
     *
73
     * @return \League\Event\Emitter
74
     */
75
    public function getEventEmitter()
76
    {
77 9
        return $this->eventEmitter;
78
    }
79 9
80
    /**
81
     * Add an item.
82
     *
83
     * @param  \jamesdb\Cart\CartItem $item
84
     *
85
     * @return string
86
     */
87
    public function add(CartItem $item)
88
    {
89
        $rowId = $item->getRowId();
90 9
91
        if ($row = $this->getItem($rowId)) {
92 9
            $row->quantity += $item->quantity;
0 ignored issues
show
Documentation introduced by
The property quantity does not exist on object<jamesdb\Cart\CartItem>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
93 9
        } else {
94
            $this->contents[$rowId] = $item;
95
        }
96
97
        $this->storage->store($this->identifier, serialize($this->toArray()));
98
99
        $this->getEventEmitter()->emit(new CartEvent\CartItemAddEvent($this, $item));
100 36
101
        return $rowId;
102 36
    }
103
104
    /**
105
     * Remove an item.
106
     *
107
     * @param  string $rowId
108
     *
109
     * @throws \jamesdb\Cart\Exception\CartRemoveItemException
110
     *
111
     * @return boolean
112 36
     */
113
    public function remove($rowId)
114 36
    {
115
        $item = $this->getItem($rowId);
116 36
117 6
        if ($item === null) {
118 6
            throw new CartException\CartItemRemoveException(
119 36
                sprintf('No such item with rowid (%s).', $rowId)
120
            );
121
        }
122 36
123
        unset($this->contents[$rowId]);
124 36
125
        $this->storage->store($this->identifier, serialize($this->contents));
126 36
127
        $this->getEventEmitter()->emit(new CartEvent\CartItemRemoveEvent($this, $item));
0 ignored issues
show
Documentation introduced by
$item is of type array, but the function expects a object<jamesdb\Cart\CartItem>.

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...
128
129
        return true;
130
    }
131
132
    /**
133
     * Update an item stored in the Cart.
134
     *
135
     * @param  string $rowId
136
     * @param  array  $data
137
     *
138 9
     * @throws \jamesdb\Cart\Exception\CartUpdateException
139
     *
140 9
     * @return boolean
141
     */
142 9
    public function update($rowId, array $data = [])
143 3
    {
144 3
        $row = $this->getItem($rowId);
145 3
146
        if ($row === null) {
147
            throw new CartException\CartItemUpdateException(
148 6
                sprintf('Could not update item (%s).', $rowId)
149
            );
150 6
        }
151
152 6
        foreach ($data as $key => $value) {
153
            $row->{$key} = $value;
154 6
        }
155
156
        $this->getEventEmitter()->emit(new CartEvent\CartItemUpdateEvent($this, $row));
0 ignored issues
show
Documentation introduced by
$row is of type array, but the function expects a object<jamesdb\Cart\CartItem>.

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...
157
158
        return true;
159
    }
160
161
    /**
162
     * Clear the cart.
163
     *
164
     * @return void
165
     */
166
    public function clear()
167 9
    {
168
        $this->storage->clear($this->identifier);
169 9
170
        $this->contents = [];
171 9
    }
172 3
173 3
    /**
174 3
     * Get a specific item from the cart.
175
     *
176
     * @param  string $rowId
177 6
     *
178 6
     * @return array|null
179 6
     */
180
    public function getItem($rowId)
181 6
    {
182
        if (array_key_exists($rowId, $this->contents)) {
183 6
            return $this->contents[$rowId];
184
        }
185
186
        return null;
187
    }
188
189
    /**
190
     * Return items.
191 72
     *
192
     * @return array
193 72
     */
194
    public function getItems()
195 72
    {
196 72
        return $this->contents;
197
    }
198
199
    /**
200
     * Return a filtered array of cart items.
201
     *
202
     * @param  string $key
203
     * @param  mixed  $value
204
     *
205 45
     * @return array
206
     */
207 45
    public function filter($key, $value)
208 21
    {
209
        return array_filter($this->contents, function(CartItem $item) use ($key, $value) {
210
            if ((isset($item[$key])) && ($item[$key] === $value)) {
211 42
                return $item;
212
            }
213
        });
214
    }
215
216
    /**
217
     * Return the total amount of unique items.
218
     *
219 3
     * @return integer
220
     */
221 3
    public function getTotalUniqueItems()
222
    {
223
        return count($this->contents);
224
    }
225
226
    /**
227
     * Return the total amount of items.
228
     *
229
     * @return integer
230
     */
231
    public function getTotalItems()
232 3
    {
233
        return array_sum(
234
            array_map(function(CartItem $item) {
235 3
                return $item->quantity;
0 ignored issues
show
Documentation introduced by
The property quantity does not exist on object<jamesdb\Cart\CartItem>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
236 3
            }, $this->contents)
237
        );
238 3
    }
239
240
    /**
241
     * Returns whether the cart is empty.
242
     *
243
     * @return boolean
244
     */
245
    public function isEmpty()
246 18
    {
247
        return ($this->getTotalUniqueItems() === 0);
248 18
    }
249
250
    /**
251
     * Return the price including tax.
252
     *
253
     * @return integer
254
     */
255
    public function getTotalPrice()
256 9
    {
257
        $total = new Money(array_sum(
258 9
            array_map(function(CartItem $item) {
259
                return $item->getPrice($this->getCurrency())->getAmount();
260 9
            }, $this->contents)
261 9
        ), $this->getCurrency());
262 9
263
        return $total->getConvertedAmount();
264
    }
265
266
    /**
267
     * Return the price excluding tax.
268
     *
269
     * @return integer
270 9
     */
271
    public function getTotalPriceExcludingTax()
272 9
    {
273
        $total = new Money(array_sum(
274
            array_map(function(CartItem $item) {
275
                return $item->getPriceExcludingTax($this->getCurrency())->getAmount();
276
            }, $this->contents)
277
        ), $this->getCurrency());
278
279
        return $total->getConvertedAmount();
280 3
    }
281
282 3
    /**
283
     * Return the carts total tax.
284 3
     *
285 3
     * @return integer
286 3
     */
287
    public function getTotalTax()
288 3
    {
289
        $total = new Money(array_sum(
290
            array_map(function(CartItem $item) {
291
                return $item->getTax($this->getCurrency())->getAmount();
292
            }, $this->contents)
293
        ), $this->getCurrency());
294
295
        return $total->getConvertedAmount();
296 3
    }
297
298 3
    /**
299
     * Restore the cart from storage.
300 3
     *
301 3
     * @throws \jamesdb\Cart\Exception\CartRestoreException
302 3
     *
303
     * @return boolean
304 3
     */
305
    public function restore()
306
    {
307
        $data = $this->storage->get($this->identifier);
308
309
        if (! empty($data)) {
310
            $data = unserialize($data);
311
312 3
            if (is_array($data) && is_string($data['id']) && is_array($data['items'])) {
313
                foreach ($data['items'] as $item) {
314 3
                    $this->contents[$item['id']] = new CartItem($item['data']);
315
                }
316 3
317 3
                return true;
318 3
            }
319
320 3
            throw new CartException\CartRestoreException(
321
                sprintf(
322
                    'Unable to restore cart [%s] from storage, ensure id is a string and the items are an array',
323
                    $this->identifier
324
                )
325
            );
326
        }
327
328
        return false;
329
    }
330 72
331
    /**
332 72
     * Export the cart to array.
333
     *
334 72
     * @return array
335 18
     */
336
    public function toArray()
337 18
    {
338 3
        return [
339
            'id'    => $this->identifier,
340 3
            'items' => array_map(function (CartItem $item) {
341
                return $item->toArray();
342
            }, $this->contents)
343 15
        ];
344 6
    }
345
}
346