Passed
Push — master ( 156190...17a9a7 )
by bader
02:48
created

Visits   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 470
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 128
dl 0
loc 470
rs 3.36
c 0
b 0
f 0
wmc 63

28 Methods

Rating   Name   Duplication   Size   Complexity  
A top() 0 8 3
A recordedIp() 0 3 1
A period() 0 7 2
A referer() 0 5 1
A newExpiration() 0 20 5
A seconds() 0 5 1
A __get() 0 7 3
A country() 0 5 1
A freshList() 0 16 2
A count() 0 12 4
A forceIncrement() 0 3 1
A __construct() 0 11 1
A countries() 0 5 2
A periodsSync() 0 11 3
A reset() 0 3 1
A refs() 0 5 2
A decrement() 0 3 1
A fresh() 0 5 1
A cachedList() 0 3 1
B getCountry() 0 24 7
B increment() 0 25 8
A noExpiration() 0 3 2
A by() 0 13 3
A getVisits() 0 5 2
A low() 0 3 1
A expireAt() 0 4 1
A forceDecrement() 0 3 1
A timeLeft() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like Visits 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.

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 Visits, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace awssat\Visits;
4
5
use Carbon\Carbon;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Support\Facades\Redis;
8
use Spatie\Referer\Referer;
9
10
class Visits
11
{
12
    /**
13
     * @var mixed
14
     */
15
    protected $ipSeconds;
16
    /**
17
     * @var null
18
     */
19
    protected $subject;
20
    /**
21
     * @var bool|mixed
22
     */
23
    protected $fresh = false;
24
    /**
25
     * @var null
26
     */
27
    protected $country = null;
28
    /**
29
     * @var null
30
     */
31
    protected $referer = null;
32
    /**
33
     * @var mixed
34
     */
35
    protected $periods;
36
    /**
37
     * @var Keys
38
     */
39
    protected $keys;
40
    /**
41
     * @var Redis
42
     */
43
    public $redis;
44
45
    /**
46
     * Visits constructor.
47
     * @param $subject
48
     * @param string $tag|null
49
     */
50
    public function __construct($subject = null, $tag = 'visits')
51
    {
52
        $config = config('visits');
53
        $this->redis = Redis::connection($config['connection']);
54
        $this->periods = $config['periods'];
55
        $this->ipSeconds = $config['remember_ip'];
56
        $this->fresh = $config['always_fresh'];
57
        $this->subject = $subject;
58
        $this->keys = new Keys($subject, $tag);
59
60
        $this->periodsSync();
61
    }
62
63
    /**
64
     * @param $attribute
65
     * @return $this
66
     */
67
    public function __get($attribute)
68
    {
69
        if($this->keys->instanceOfModel && $relation = $this->subject->$attribute) {
70
            $this->keys->append($attribute, $relation->{$relation->getKeyName()});
71
        }
72
73
        return $this;
74
    }
75
76
    /**
77
     * @param $subject
78
     * @return $this
79
     */
80
    public function by($subject)
81
    {
82
        if($subject instanceof Model) {
83
            $this->keys->append(strtolower(str_singular(class_basename(get_class($subject)))),
84
                $subject->{$subject->getKeyName()});
85
        } else if (is_array($subject)) {
86
            $subject = collect($subject);
87
            $this->keys->append($subject->flip()->first(), $subject->first());
88
        } else {
89
            $this->keys->append('custom', $subject);
90
        }
91
92
        return $this;
93
    }
94
95
    /**
96
     * Return fresh cache from database
97
     * @return $this
98
     */
99
    public function fresh()
100
    {
101
        $this->fresh = true;
102
103
        return $this;
104
    }
105
106
    /**
107
     * set x seconds for ip expiration
108
     *
109
     * @param $seconds
110
     * @return $this
111
     */
112
    public function seconds($seconds)
113
    {
114
        $this->ipSeconds = $seconds;
115
116
        return $this;
117
    }
118
119
120
    /**
121
     * @param $country
122
     * @return $this
123
     */
124
    public function country($country)
125
    {
126
        $this->country = $country;
127
128
        return $this;
129
    }
130
131
132
    /**
133
     * @param $referer
134
     * @return $this
135
     */
136
    public function referer($referer)
137
    {
138
        $this->referer = $referer;
139
140
        return $this;
141
    }
142
143
    /**
144
     * Change period
145
     *
146
     * @param $period
147
     * @return $this
148
     */
149
    public function period($period)
150
    {
151
        if (in_array($period, array_keys($this->periods))) {
152
            $this->keys->visits = $this->keys->period($period);
153
        }
154
155
        return $this;
156
    }
157
158
    /**
159
     * Sync periods times
160
     */
161
    protected function periodsSync()
162
    {
163
        foreach ($this->periods as $period) {
164
            $periodKey = $this->keys->period($period);
165
166
            if ($this->noExpiration($periodKey)) {
167
                $expireInSeconds = $this->newExpiration($period);
168
                $this->redis->incrby($periodKey . '_total', 0);
169
                $this->redis->zincrby($periodKey, 0, 0);
170
                $this->redis->expire($periodKey, $expireInSeconds);
171
                $this->redis->expire($periodKey . '_total', $expireInSeconds);
172
            }
173
        }
174
    }
175
176
    /**
177
     * @param $periodKey
178
     * @return bool
179
     */
180
    protected function noExpiration($periodKey)
181
    {
182
        return $this->redis->ttl($periodKey) == -1 || !$this->redis->exists($periodKey);
183
    }
184
185
    /**
186
     * @param $period
187
     * @return int
188
     */
189
    protected function newExpiration($period)
190
    {
191
        $expireInSeconds = 0;
192
193
        switch ($period) {
194
            case 'day':
195
                $expireInSeconds = Carbon::now()->endOfDay()->timestamp - Carbon::now()->timestamp;
196
                break;
197
            case 'week':
198
                $expireInSeconds = Carbon::now()->endOfWeek()->timestamp - Carbon::now()->timestamp;
199
                break;
200
            case 'month':
201
                $expireInSeconds = Carbon::now()->endOfMonth()->timestamp - Carbon::now()->timestamp;
202
                break;
203
            case 'year':
204
                $expireInSeconds = Carbon::now()->endOfYear()->timestamp - Carbon::now()->timestamp;
205
                break;
206
        }
207
208
        return $expireInSeconds + 1;
209
    }
210
211
    /**
212
     * Reset methods
213
     *
214
     * @param $method
215
     * @param string $args
216
     * @return Reset
217
     */
218
    public function reset($method = 'visits', $args = '')
219
    {
220
        return new Reset($this, $method, $args);
221
    }
222
223
    /**
224
     * Fetch all time trending subjects.
225
     *
226
     * @param int $limit
227
     * @param bool $isLow
228
     * @return \Illuminate\Support\Collection|array
229
     */
230
    public function top($limit = 5, $isLow = false)
231
    {
232
        $visitsIds = $this->getVisits($limit, $this->keys->visits, $isLow);
233
        $cacheKey = $this->keys->cache($limit, $isLow);
234
        $cachedList = $this->cachedList($limit, $cacheKey);
235
        $cachedIds = $cachedList->pluck('id')->toArray();
236
237
        return ($visitsIds === $cachedIds && !$this->fresh) ? $cachedList : $this->freshList($cacheKey, $visitsIds);
238
    }
239
240
241
    /**
242
     * Top/low countries
243
     *
244
     * @param int $limit
245
     * @param bool $isLow
246
     * @return mixed
247
     */
248
    public function countries($limit = -1, $isLow = false)
249
    {
250
        $range = $isLow ? 'zrange' : 'zrevrange';
251
252
        return $this->redis->$range($this->keys->visits . "_countries:{$this->keys->id}", 0, $limit, 'WITHSCORES');
253
    }
254
255
    /**
256
     * top/lows refs
257
     *
258
     * @param int $limit
259
     * @param bool $isLow
260
     * @return mixed
261
     */
262
    public function refs($limit = -1, $isLow = false)
263
    {
264
        $range = $isLow ? 'zrange' : 'zrevrange';
265
266
        return $this->redis->$range($this->keys->visits . "_referers:{$this->keys->id}", 0, $limit, 'WITHSCORES');
267
    }
268
269
    /**
270
     * Fetch lowest subjects.
271
     *
272
     * @param int $limit
273
     * @return \Illuminate\Support\Collection|array
274
     */
275
    public function low($limit = 5)
276
    {
277
        return $this->top($limit, true);
278
    }
279
280
    /**
281
     * Check for the ip is has been recorded before
282
     *
283
     * @return bool
284
     * @internal param $subject
285
     */
286
    public function recordedIp()
287
    {
288
        return !$this->redis->set($this->keys->ip(request()->ip()), true, 'EX', $this->ipSeconds, 'NX');
289
    }
290
291
    /**
292
     * Get visits of model instance.
293
     *
294
     * @return mixed
295
     * @internal param $subject
296
     */
297
    public function count()
298
    {
299
        if ($this->country) {
300
            return $this->redis->zscore($this->keys->visits . "_countries:{$this->keys->id}", $this->country);
301
        } else if ($this->referer) {
302
            return $this->redis->zscore($this->keys->visits . "_referers:{$this->keys->id}", $this->referer);
303
        }
304
305
        return intval(
306
            (!$this->keys->instanceOfModel) ?
307
                $this->redis->get($this->keys->visits . '_total') :
308
                $this->redis->zscore($this->keys->visits, $this->keys->id)
309
        );
310
    }
311
312
    /**
313
     * use diffForHumans to show diff
314
     * @param $period
315
     * @return Carbon
316
     */
317
    public function timeLeft($period = false)
318
    {
319
        return Carbon::now()->addSeconds($this->redis->ttl(
320
            $period ? $this->keys->period($period) : $this->keys->ip(request()->ip())
321
        ));
322
    }
323
324
325
    /**
326
     * Increment a new/old subject to the cache cache.
327
     *
328
     * @param int $inc
329
     * @param bool $force
330
     * @param bool $periods
331
     * @param bool $country
332
     * @param bool $refer
333
     */
334
    public function increment($inc = 1, $force = false, $periods = true, $country = true, $ip = null, $refer = true)
335
    {
336
        if ($force || !$this->recordedIp()) {
337
            $this->redis->zincrby($this->keys->visits, $inc, $this->keys->id);
338
            $this->redis->incrby($this->keys->visits . '_total', $inc);
339
340
341
            if ($country) {
342
                $zz = $this->getCountry($ip);
343
                $this->redis->zincrby($this->keys->visits . "_countries:{$this->keys->id}", $inc, $zz);
344
            }
345
346
347
            $referer = app(Referer::class)->get();
348
349
            if ($refer && !empty($referer)) {
350
                $this->redis->zincrby($this->keys->visits . "_referers:{$this->keys->id}", $inc, $referer);
351
            }
352
353
            if ($periods) {
354
                foreach ($this->periods as $period) {
355
                    $periodKey = $this->keys->period($period);
356
357
                    $this->redis->zincrby($periodKey, $inc, $this->keys->id);
358
                    $this->redis->incrby($periodKey . '_total', $inc);
359
                }
360
            }
361
        }
362
    }
363
364
365
    /**
366
     * @param int $inc
367
     * @param bool $periods
368
     */
369
    public function forceIncrement($inc = 1, $periods = true)
370
    {
371
        $this->increment($inc, true, $periods);
372
    }
373
374
    /**
375
     * Decrement a new/old subject to the cache cache.
376
     *
377
     * @param int $dec
378
     * @param bool $force
379
     */
380
    public function decrement($dec = 1, $force = false)
381
    {
382
        $this->increment(-$dec, $force);
383
    }
384
385
    /**
386
     * @param int $dec
387
     * @param bool $periods
388
     */
389
    public function forceDecrement($dec = 1, $periods = true)
390
    {
391
        $this->increment(-$dec, true, $periods);
392
    }
393
394
    /**
395
     * @param $limit
396
     * @param $visitsKey
397
     * @param bool $isLow
398
     * @return mixed
399
     */
400
    protected function getVisits($limit, $visitsKey, $isLow = false)
401
    {
402
        $range = $isLow ? 'zrange' : 'zrevrange';
403
404
        return array_map('intval', $this->redis->$range($visitsKey, 0, $limit - 1));
405
    }
406
407
    /**
408
     * @param $cacheKey
409
     * @param $visitsIds
410
     * @return mixed
411
     */
412
    protected function freshList($cacheKey, $visitsIds)
413
    {
414
        if (count($visitsIds)) {
415
            $this->redis->del($cacheKey);
416
417
            return ($this->subject)::whereIn($this->keys->primary, $visitsIds)
418
                ->get()
419
                ->sortBy(function ($subject) use ($visitsIds) {
420
                    return array_search($subject->{$this->keys->primary}, $visitsIds);
421
                })
422
                ->each(function ($subject) use ($cacheKey) {
423
                    $this->redis->rpush($cacheKey, serialize($subject));
424
                });
425
        }
426
427
        return [];
428
    }
429
430
    /**
431
     * @param $limit
432
     * @param $cacheKey
433
     * @return \Illuminate\Support\Collection|array
434
     */
435
    protected function cachedList($limit, $cacheKey)
436
    {
437
        return collect(array_map('unserialize', $this->redis->lrange($cacheKey, 0, $limit - 1)));
438
    }
439
440
441
    /**
442
     *  Gets visitor country code
443
     * @return mixed|string
444
     */
445
    public function getCountry($ip = null)
446
    {
447
        if (session('country_code')) {
448
            return session('country_code');
449
        }
450
451
        $country_code = 'zz';
452
453
        if (isset($_SERVER["HTTP_CF_IPCOUNTRY"])) {
454
            $country_code = strtolower($_SERVER["HTTP_CF_IPCOUNTRY"]);
455
        }
456
457
        if ($country_code === 'zz' && app()->has('geoip')) {
458
459
            $geo_info = geoip()->getLocation($ip);
460
461
            if (!empty($geo_info) && isset($geo_info['iso_code'])) {
462
                $country_code = strtolower($geo_info['iso_code']);
463
            }
464
465
        }
466
467
        session(['country_code', $country_code]);
468
        return $country_code;
469
    }
470
471
    /**
472
     * @param $period
473
     * @param int $time
474
     * @return bool
475
     */
476
    public function expireAt($period, $time)
477
    {
478
        $periodKey = $this->keys->period($period);
479
        return $this->redis->expire($periodKey, $time);
480
    }
481
}
482