RemoteCartStorage::getCacheKey()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * Finance module for HiPanel
4
 *
5
 * @link      https://github.com/hiqdev/hipanel-module-finance
6
 * @package   hipanel-module-finance
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2015-2019, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hipanel\modules\finance\cart\storage;
12
13
use hipanel\components\SettingsStorage;
14
use hipanel\helpers\StringHelper;
15
use Yii;
16
use yii\base\InvalidConfigException;
17
use yii\base\NotSupportedException;
18
use yii\caching\CacheInterface;
19
use yii\helpers\Json;
20
use yii\web\MultiFieldSession;
21
use yii\web\Session;
22
use yii\web\User;
23
24
/**
25
 * Class RemoteCartStorage.
26
 *
27
 * @author Dmytro Naumenko <[email protected]>
28
 */
29
class RemoteCartStorage extends MultiFieldSession implements CartStorageInterface
30
{
31
    /**
32
     * @var string The cart name. Used to distinguish carts, if there are different carts stored.
33
     * E.g. site, panel and mobile-app cart.
34
     */
35
    public $sessionCartId;
36
37
    const CACHE_DURATION = 60 * 60; // 1 hour
38
    /**
39
     * @var array
40
     */
41
    protected $data = [];
42
    /**
43
     * @var array
44
     */
45
    protected $oldData = [];
46
    /**
47
     * @var SettingsStorage
48
     */
49
    private $settingsStorage;
50
    /**
51
     * @var CacheInterface
52
     */
53
    private $cache;
54
    /**
55
     * @var User
56
     */
57
    private $user;
58
59
    /**
60
     * @var Session
61
     */
62
    private $session;
63
64
    public function __construct(Session $session, User $user, SettingsStorage $settingsStorage, CacheInterface $cache, array $config = [])
65
    {
66
        $this->settingsStorage = $settingsStorage;
67
        $this->cache = $cache;
68
        $this->user = $user;
69
        $this->session = $session;
70
71
        parent::__construct($config);
72
    }
73
74
    public function init()
75
    {
76
        parent::init();
77
78
        if (empty($this->sessionCartId)) {
79
            throw new InvalidConfigException('Parameter "sessionCartId" must be set in RemoteCartStorage');
80
        }
81
    }
82
83
    protected function read()
84
    {
85
        try {
86
            $this->data = $this->cache->getOrSet($this->getCacheKey(), function () {
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->cache->getOrSet($..., self::CACHE_DURATION) of type * is incompatible with the declared type array of property $data.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
87
                $remoteCartData = $this->settingsStorage->getBounded($this->getStorageKey());
88
                if ($remoteCartData === []) {
89
                    return $this->session;
90
                } elseif (!is_string($remoteCartData)) {
91
                    Yii::warning('Remote cart data is neither empty array nor string. See: ' . var_export($remoteCartData, true));
92
                }
93
94
                $localCartData = $this->session[$this->sessionCartId] ?? null;
95
96
                return $this->mergedCartData($remoteCartData, $localCartData);
97
            }, self::CACHE_DURATION);
98
        } catch (\Exception $exception) {
99
            Yii::error('Failed to read cart: ' . $exception->getMessage(), __METHOD__);
100
        }
101
    }
102
103
    /**
104
     * @param string $remoteData base64 encoded JSON of serialized remotely stored cart items
105
     * @param string $localData local cart items array. Defaults to `null`, meaning no local data exists
106
     * @return array
107
     */
108
    private function mergedCartData($remoteData, $localData = null)
109
    {
110
        $decodedRemote = Json::decode(base64_decode($remoteData, true));
111
112
        $local = $localData ? unserialize($localData) : [];
113
        $remote = isset($decodedRemote[$this->sessionCartId])
114
            ? unserialize($decodedRemote[$this->sessionCartId])
115
            : [];
116
117
        /** @noinspection AdditionOperationOnArraysInspection */
118
        return [$this->sessionCartId => serialize($remote + $local)];
119
    }
120
121
    protected function getCacheKey()
122
    {
123
        return [__CLASS__, $this->user->getId(), session_id()];
124
    }
125
126
    protected function getStorageKey()
127
    {
128
        return StringHelper::basename(__CLASS__);
129
    }
130
131
    protected function write()
132
    {
133
        if ($this->data === $this->oldData) {
134
            return;
135
        }
136
137
        try {
138
            $this->cache->set($this->getCacheKey(), $this->data);
139
            $this->settingsStorage->setBounded($this->getStorageKey(), base64_encode(Json::encode($this->data)));
140
        } catch (\Exception $exception) {
141
            Yii::error('Failed to save cart: ' . $exception->getMessage(), __METHOD__);
142
        }
143
    }
144
145
    /**
146
     * @var bool
147
     */
148
    private $_isActive;
149
150
    public function getIsActive()
151
    {
152
        return $this->_isActive;
153
    }
154
155
    /** {@inheritdoc} */
156
    public function open()
157
    {
158
        if ($this->getIsActive()) {
159
            return;
160
        }
161
162
        $this->read();
163
        $this->oldData = $this->data;
164
        $this->_isActive = true;
165
        $this->registerShutdownFunction();
166
    }
167
168
    /** {@inheritdoc} */
169
    public function offsetExists($offset)
170
    {
171
        $this->open();
172
173
        return isset($this->data[$offset]);
174
    }
175
176
    /** {@inheritdoc} */
177
    public function offsetGet($offset)
178
    {
179
        $this->open();
180
181
        return $this->data[$offset] ?? null;
182
    }
183
184
    /** {@inheritdoc} */
185
    public function offsetSet($offset, $item)
186
    {
187
        $this->open();
188
189
        $this->data[$offset] = $item;
190
    }
191
192
    /** {@inheritdoc} */
193
    public function offsetUnset($offset)
194
    {
195
        $this->open();
196
        unset($this->data[$offset]);
197
    }
198
199
    /**
200
     * @throws NotSupportedException
201
     * @void
202
     */
203
    private function throwShouldNotBeCalledException()
204
    {
205
        throw new NotSupportedException('Remote cart storage extends yii\web\Session, but it is not a session actually. This method should be never called.');
206
    }
207
208
    /** {@inheritdoc} */
209
    public function regenerateID($deleteOldSession = false)
210
    {
211
        $this->throwShouldNotBeCalledException();
212
    }
213
214
    /** {@inheritdoc} */
215
    public function readSession($id)
216
    {
217
        return $this->throwShouldNotBeCalledException();
218
    }
219
220
    /** {@inheritdoc} */
221
    public function writeSession($id, $data)
222
    {
223
        return $this->throwShouldNotBeCalledException();
224
    }
225
226
    /** {@inheritdoc} */
227
    public function destroySession($id)
228
    {
229
        return $this->throwShouldNotBeCalledException();
230
    }
231
232
    /** {@inheritdoc} */
233
    public function gcSession($maxLifetime)
234
    {
235
        return $this->throwShouldNotBeCalledException();
236
    }
237
238
    private function registerShutdownFunction()
239
    {
240
        register_shutdown_function(\Closure::fromCallable([$this, 'write']));
241
    }
242
}
243