Completed
Push — master ( f328e9...1657f1 )
by ARCANEDEV
13s
created

SessionTracker   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 370
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 60.48%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 4
dl 0
loc 370
ccs 75
cts 124
cp 0.6048
rs 7.9487
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getSessionKey() 0 4 1
A setSessionData() 0 8 2
A getSessionId() 0 4 1
A setSessionId() 0 4 1
A track() 0 6 1
A checkData() 0 6 4
A updateData() 0 7 1
A getSessionData() 0 6 2
B checkIfUserChanged() 0 18 6
A createSessionForGuest() 0 11 1
A regenerateSystemSession() 0 11 3
A resetSessionUuid() 0 13 2
A putSessionData() 0 6 1
A checkSessionUuid() 0 5 3
A getSystemSessionId() 0 4 1
A createSessionIfIsUnknown() 0 18 2
A sessionIsKnown() 0 13 4
A findByUuid() 0 4 1
A generateSession() 0 9 2
B checkSessionDataIsReliable() 0 15 7
B ensureSessionDataIsComplete() 0 21 5

How to fix   Complexity   

Complex Class

Complex classes like SessionTracker 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 SessionTracker, and based on these observations, apply Extract Interface, too.

1
<?php namespace Arcanedev\LaravelTracker\Trackers;
2
3
use Arcanedev\LaravelTracker\Contracts\Trackers\SessionTracker as SessionTrackerContract;
4
use Arcanedev\LaravelTracker\Models\Session;
5
use Carbon\Carbon;
6
use Illuminate\Session\Store as SessionStore;
7
use Illuminate\Support\Arr;
8
use Ramsey\Uuid\Uuid;
9
10
/**
11
 * Class     SessionTracker
12
 *
13
 * @package  Arcanedev\LaravelTracker\Trackers
14
 * @author   ARCANEDEV <[email protected]>
15
 */
16
class SessionTracker implements SessionTrackerContract
17
{
18
    /* ------------------------------------------------------------------------------------------------
19
     |  Properties
20
     | ------------------------------------------------------------------------------------------------
21
     */
22
    /** @var \Illuminate\Session\Store */
23
    private $session;
24
25
    /** @var array */
26
    private $sessionInfo = [];
27
28
    /* ------------------------------------------------------------------------------------------------
29
     |  Constructor
30
     | ------------------------------------------------------------------------------------------------
31
     */
32
    /**
33
     * SessionTracker constructor.
34
     *
35
     * @param  \Illuminate\Session\Store  $session
36
     */
37 18
    public function __construct(SessionStore $session)
38
    {
39 18
        $this->session = $session;
40 18
    }
41
42
    /* ------------------------------------------------------------------------------------------------
43
     |  Getters & Setters
44
     | ------------------------------------------------------------------------------------------------
45
     */
46
    /**
47
     * Get the session key.
48
     *
49
     * @return string
50
     */
51 12
    private function getSessionKey()
52
    {
53 12
        return config('laravel-tracker.session.name', 'session_name_here');
54
    }
55
56
    /**
57
     * Set the session data.
58
     *
59
     * @param  array  $data
60
     */
61 12
    private function setSessionData(array $data)
62
    {
63 12
        $this->generateSession($data);
64
65 12
        if ($this->createSessionIfIsUnknown()) {
66
            $this->ensureSessionDataIsComplete();
67
        }
68 12
    }
69
70
    /**
71
     * Get the session id.
72
     *
73
     * @return int
74
     */
75 12
    private function getSessionId()
76
    {
77 12
        return $this->sessionInfo['id'];
78
    }
79
80
    /**
81
     * Set the session id.
82
     *
83
     * @param  mixed  $id
84
     */
85 12
    private function setSessionId($id)
86
    {
87 12
        $this->sessionInfo['id'] = $id;
88 12
    }
89
90
    /* ------------------------------------------------------------------------------------------------
91
     |  Main Functions
92
     | ------------------------------------------------------------------------------------------------
93
     */
94
    /**
95
     * Track the session.
96
     *
97
     * @param  array  $data
98
     *
99
     * @return int
100
     */
101 12
    public function track(array $data)
102
    {
103 12
        $this->setSessionData($data);
104
105 12
        return $this->getSessionId();
106
    }
107
108
    /**
109
     * Check the session data.
110
     *
111
     * @param  array  $currentData
112
     * @param  array  $newData
113
     *
114
     * @return array
115
     */
116 6
    public function checkData(array $currentData, array $newData)
117
    {
118 6
        return ($currentData && $newData && $currentData !== $newData)
0 ignored issues
show
Bug Best Practice introduced by
The expression $currentData 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...
Bug Best Practice introduced by
The expression $newData 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...
119 6
            ? $this->updateData($newData)
120 6
            : $newData;
121
    }
122
123
    /* ------------------------------------------------------------------------------------------------
124
     |  Other Functions
125
     | ------------------------------------------------------------------------------------------------
126
     */
127
    /**
128
     * Update the session data.
129
     *
130
     * @param  array  $data
131
     *
132
     * @return array
133
     */
134 6
    private function updateData(array $data)
135
    {
136 6
        $this->checkIfUserChanged($data)
137 6
             ->update(Arr::except($data, ['id', 'uuid']));
138
139 6
        return $data;
140
    }
141
142
    /**
143
     * Get the session data.
144
     *
145
     * @param  string|null  $column
146
     *
147
     * @return mixed
148
     */
149 12
    private function getSessionData($column = null)
150
    {
151 12
        $data = $this->session->get($this->getSessionKey());
152
153 12
        return is_null($column) ? $data : Arr::get($data, $column, null);
154
    }
155
156
    /**
157
     * Check if user changed.
158
     *
159
     * @param  array  $data
160
     *
161
     * @return \Arcanedev\LaravelTracker\Models\Session
162
     */
163 6
    private function checkIfUserChanged(array $data)
164
    {
165 6
        $model = Session::find($this->getSessionData('id'));
166
167 6
        if (is_null($model) && ! $this->sessionIsKnown())
168 6
            return $this->createSessionForGuest($data);
169
170
        if (
171
            ! is_null($model->user_id) &&
172
            ! is_null($data['user_id']) &&
173
            $data['user_id'] !== $model->user_id
174
        ) {
175
            $newSession = $this->regenerateSystemSession($data);
176
            $model      = $this->findByUuid($newSession['uuid']);
177
        }
178
179
        return $model;
180
    }
181
182
    /**
183
     * Create session for guest.
184
     *
185
     * @param  array  $data
186
     *
187
     * @return \Arcanedev\LaravelTracker\Models\Session
188
     */
189 6
    private function createSessionForGuest(array $data)
190
    {
191 6
        $this->generateSession($data);
192
193 6
        $session = Session::create($this->sessionInfo);
194
195 6
        $this->putSessionData($data);
196 6
        $this->setSessionId($session->id);
197
198 6
        return $session;
199
    }
200
201
    /**
202
     * Regenerate system session.
203
     *
204
     * @param  array|null  $data
205
     *
206
     * @return array
207
     */
208
    private function regenerateSystemSession($data = null)
209
    {
210
        $data = $data ?: $this->getSessionData();
211
212
        if ($data) {
213
            $this->resetSessionUuid($data);
214
            $this->createSessionIfIsUnknown();
215
        }
216
217
        return $this->sessionInfo;
218
    }
219
220
    /**
221
     * Reset the session uuid.
222
     *
223
     * @param  array|null  $data
224
     *
225
     * @return array|null
226
     */
227
    private function resetSessionUuid($data = null)
228
    {
229
        $this->sessionInfo['uuid'] = null;
230
231
        $data = $data ?: $this->sessionInfo;
232
233
        unset($data['uuid']);
234
235
        $this->putSessionData($data);
236
        $this->checkSessionUuid();
237
238
        return $data;
239
    }
240
241
    /**
242
     * Put the session data.
243
     *
244
     * @param  array  $data
245
     */
246 12
    private function putSessionData(array $data)
247
    {
248 12
        $this->session->put([
249 12
            $this->getSessionKey() => $data
250 6
        ]);
251 12
    }
252
253
    /**
254
     * Check the session uuid.
255
     */
256 12
    private function checkSessionUuid()
257
    {
258 12
        if ( ! isset($this->sessionInfo['uuid']) || ! $this->sessionInfo['uuid'])
259 12
            $this->sessionInfo['uuid'] = $this->getSystemSessionId();
260 12
    }
261
262
    /**
263
     * Get the system session id.
264
     *
265
     * @return string
266
     */
267 12
    private function getSystemSessionId()
268
    {
269 12
        return Arr::get($this->getSessionData(), 'uuid', (string) Uuid::uuid4());
270
    }
271
272
    /**
273
     * @return bool
274
     */
275 12
    private function createSessionIfIsUnknown()
276
    {
277
        /** @var \Arcanedev\LaravelTracker\Models\Session $session */
278 12
        if ($known = $this->sessionIsKnown()) {
279
            $session = Session::find($id = $this->getSessionData('id'));
280
            $session->updated_at = Carbon::now();
281
            $session->save();
282
283
            $this->setSessionId($id);
284
        }
285
        else {
286 12
            $session = Session::firstOrCreate(Arr::only($this->sessionInfo, ['uuid']), $this->sessionInfo);
287 12
            $this->setSessionId($session->id);
288 12
            $this->putSessionData($this->sessionInfo);
289
        }
290
291 12
        return $known;
292
    }
293
294
    /**
295
     * Check if the session is known.
296
     *
297
     * @return bool
298
     */
299 12
    private function sessionIsKnown()
300
    {
301 12
        if ( ! $this->session->has($this->getSessionKey()))
302 12
            return false;
303
304 6
        if ($this->getSessionData('uuid') != $this->getSystemSessionId())
305 6
            return false;
306
307
        if ( ! $this->findByUuid($this->getSessionData('uuid')))
308
            return false;
309
310
        return true;
311
    }
312
313
    /**
314
     * Find a session by its uuid.
315
     *
316
     * @param  string  $uuid
317
     *
318
     * @return \Arcanedev\LaravelTracker\Models\Session
319
     */
320
    private function findByUuid($uuid)
321
    {
322
        return Session::where('uuid', $uuid)->first();
323
    }
324
325
    /**
326
     * Generate session data.
327
     *
328
     * @param  array  $sessionInfo
329
     */
330 12
    private function generateSession(array $sessionInfo)
331
    {
332 12
        $this->sessionInfo = $sessionInfo;
333
334 12
        if ( ! $this->checkSessionDataIsReliable())
335 6
            $this->regenerateSystemSession();
336
337 12
        $this->checkSessionUuid();
338 12
    }
339
340
    /**
341
     * Check if the session data is reliable.
342
     *
343
     * @return bool
344
     */
345 12
    private function checkSessionDataIsReliable()
346
    {
347 12
        $data = $this->getSessionData();
348
349 12
        if (isset($data['user_id']) && ($data['user_id'] !== $this->sessionInfo['user_id']))
350 6
            return false;
351
352 12
        if (isset($data['client_ip']) && ($data['client_ip'] !== $this->sessionInfo['client_ip']))
353 6
            return false;
354
355 12
        if (isset($data['user_agent']) && ($data['user_agent'] !== $this->sessionInfo['user_agent']))
356 6
            return false;
357
358 12
        return true;
359
    }
360
361
    /**
362
     * Ensure that the session data is complete.
363
     */
364
    private function ensureSessionDataIsComplete()
365
    {
366
        $sessionData = $this->getSessionData();
367
        $completed   = true;
368
369
        foreach ($this->sessionInfo as $key => $value) {
370
            if ($sessionData[$key] !== $value) {
371
                /** @var \Arcanedev\LaravelTracker\Models\Session $model */
372
                if ( ! isset($model))
373
                    $model = Session::find($this->getSessionId());
374
375
                $model->setAttribute($key, $value);
376
                $model->save();
377
378
                $completed = false;
379
            }
380
        }
381
382
        if ( ! $completed)
383
            $this->putSessionData($this->sessionInfo);
384
    }
385
}
386