Completed
Push — master ( 5d3719...f231a9 )
by Zura
01:23
created

Promocodes::getExpiresIn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Gabievi\Promocodes;
4
5
use Carbon\Carbon;
6
use Gabievi\Promocodes\Models\Promocode;
7
use Gabievi\Promocodes\Exceptions\AlreadyUsedException;
8
use Gabievi\Promocodes\Exceptions\UnauthenticatedException;
9
use Gabievi\Promocodes\Exceptions\InvalidPromocodeException;
10
11
class Promocodes
12
{
13
    /**
14
     * Prefix for code generation
15
     *
16
     * @var string
17
     */
18
    protected $prefix;
19
20
    /**
21
     * Suffix for code generation
22
     *
23
     * @var string
24
     */
25
    protected $suffix;
26
27
    /**
28
     * Number of codes to be generated
29
     *
30
     * @var int
31
     */
32
    protected $amount = 1;
33
34
    /**
35
     * Reward value which will be sticked to code
36
     *
37
     * @var null
38
     */
39
    protected $reward = null;
40
41
    /**
42
     * Additional data to be returned with code
43
     *
44
     * @var array
45
     */
46
    protected $data = [];
47
48
    /**
49
     * Number of days of code expiration
50
     *
51
     * @var null|int
52
     */
53
    protected $expires_in = null;
54
55
    /**
56
     * Maximum number of available usage of code
57
     *
58
     * @var null|int
59
     */
60
    protected $quantity = null;
61
62
    /**
63
     * If code should automatically invalidate after first use
64
     *
65
     * @var bool
66
     */
67
    protected $disposable = false;
68
69
    /**
70
     * Generated codes will be saved here
71
     * to be validated later.
72
     *
73
     * @var array
74
     */
75
    private $codes = [];
76
77
    /**
78
     * Length of code will be calculated from asterisks you have
79
     * set as mask in your config file.
80
     *
81
     * @var int
82
     */
83
    private $length;
84
85
    /**
86
     * Promocodes constructor.
87
     */
88
    public function __construct()
89
    {
90
        $this->codes = Promocode::pluck('code')->toArray();
91
        $this->length = substr_count(config('promocodes.mask'), '*');
92
93
        $this->prefix = (bool)config('promocodes.prefix')
94
            ? config('promocodes.prefix') . config('promocodes.separator')
95
            : '';
96
97
        $this->suffix = (bool)config('promocodes.suffix')
98
            ? config('promocodes.separator') . config('promocodes.suffix')
99
            : '';
100
    }
101
102
    /**
103
     * Save one-time use promocodes into database
104
     * Successful insert returns generated promocodes
105
     * Fail will return empty collection.
106
     *
107
     * @param int $amount
108
     * @param null $reward
109
     * @param array $data
110
     * @param int|null $expires_in
111
     * @param int|null $quantity
112
     *
113
     * @return \Illuminate\Support\Collection
114
     */
115
    public function createDisposable(
116
        $amount = null,
117
        $reward = null,
118
        $data = null,
119
        $expires_in = null,
120
        $quantity = null
121
    )
122
    {
123
        return $this->create($amount, $reward, $data, $expires_in, $quantity, true);
124
    }
125
126
    /**
127
     * Save promocodes into database
128
     * Successful insert returns generated promocodes
129
     * Fail will return empty collection.
130
     *
131
     * @param int $amount
132
     * @param null $reward
133
     * @param array $data
134
     * @param int|null $expires_in
135
     * @param bool $is_disposable
136
     * @param int|null $quantity
137
     *
138
     * @return \Illuminate\Support\Collection
139
     */
140
    public function create(
141
        $amount = null,
142
        $reward = null,
143
        $data = null,
144
        $expires_in = null,
145
        $quantity = null,
146
        $is_disposable = false
147
    )
148
    {
149
        $records = [];
150
151
        foreach ($this->output($amount) as $code) {
152
            $records[] = [
153
                'code' => $code,
154
                'reward' => $this->getReward($reward),
155
                'data' => json_encode($this->getData($data)),
156
                'expires_at' => $this->getExpiresIn($expires_in) ? Carbon::now()->addDays($this->getExpiresIn($expires_in)) : null,
157
                'is_disposable' => $this->getDisposable($is_disposable),
158
                'quantity' => $this->getQuantity($quantity),
159
            ];
160
        }
161
162
        if (Promocode::insert($records)) {
163
            return collect($records)->map(function ($record) {
164
                $record['data'] = json_decode($record['data'], true);
165
166
                return $record;
167
            });
168
        }
169
170
        return collect([]);
171
    }
172
173
    /**
174
     * Generates promocodes as many as you wish.
175
     *
176
     * @param int $amount
177
     *
178
     * @return array
179
     */
180
    public function output($amount = null)
181
    {
182
        $collection = [];
183
184
        for ($i = 1; $i <= $this->getAmount($amount); $i++) {
185
            $random = $this->generate();
186
187
            while (!$this->validate($collection, $random)) {
188
                $random = $this->generate();
189
            }
190
191
            array_push($collection, $random);
192
        }
193
194
        return $collection;
195
    }
196
197
    /**
198
     * Get number of codes to be generated
199
     *
200
     * @param null|int $request
201
     * @return null|int
202
     */
203
    public function getAmount($request)
204
    {
205
        return $request !== null ? $request : $this->amount;
206
    }
207
208
    /**
209
     * Set how much code you want to be generated
210
     *
211
     * @param int $amount
212
     * @return $this
213
     */
214
    public function setAmount($amount)
215
    {
216
        $this->amount = $amount;
217
        return $this;
218
    }
219
220
    /**
221
     * Here will be generated single code using your parameters from config.
222
     *
223
     * @return string
224
     */
225
    private function generate()
226
    {
227
        $characters = config('promocodes.characters');
228
        $mask = config('promocodes.mask');
229
        $promocode = '';
230
        $random = [];
231
232
        for ($i = 1; $i <= $this->length; $i++) {
233
            $character = $characters[rand(0, strlen($characters) - 1)];
234
            $random[] = $character;
235
        }
236
237
        shuffle($random);
238
        $length = count($random);
239
240
        $promocode .= $this->prefix;
241
242
        for ($i = 0; $i < $length; $i++) {
243
            $mask = preg_replace('/\*/', $random[$i], $mask, 1);
244
        }
245
246
        $promocode .= $mask;
247
        $promocode .= $this->suffix;
248
249
        return $promocode;
250
    }
251
252
    /**
253
     * Your code will be validated to be unique for one request.
254
     *
255
     * @param $collection
256
     * @param $new
257
     *
258
     * @return bool
259
     */
260
    private function validate($collection, $new)
261
    {
262
        return !in_array($new, array_merge($collection, $this->codes));
263
    }
264
265
    /**
266
     * Get custom set reward value
267
     *
268
     * @param null|int $request
269
     * @return null|int
270
     */
271
    public function getReward($request)
272
    {
273
        return $request !== null ? $request : $this->reward;
274
    }
275
276
    /**
277
     * Set custom reward value
278
     *
279
     * @param int $reward
280
     * @return $this
281
     */
282
    public function setReward($reward)
283
    {
284
        $this->reward = $reward;
0 ignored issues
show
Documentation Bug introduced by
It seems like $reward of type integer is incompatible with the declared type null of property $reward.

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...
285
        return $this;
286
    }
287
288
    /**
289
     * Get custom set data value
290
     *
291
     * @param null|array $data
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
292
     * @return null|array
293
     */
294
    public function getData($request)
295
    {
296
        return $request !== null ? $request : $this->data;
297
    }
298
299
    /**
300
     * Set custom data value
301
     *
302
     * @param array $data
303
     * @return $this
304
     */
305
    public function setData($data)
306
    {
307
        $this->data = $data;
308
        return $this;
309
    }
310
311
    /**
312
     * Get custom set expiration days value
313
     *
314
     * @param null|int $request
315
     * @return null|int
316
     */
317
    public function getExpiresIn($request)
318
    {
319
        return $request !== null ? $request : $this->expires_in;
320
    }
321
322
    /**
323
     * Set custom expiration days value
324
     *
325
     * @param int $expires_in
326
     * @return $this
327
     */
328
    public function setExpiresIn($expires_in)
329
    {
330
        $this->expires_in = $expires_in;
331
        return $this;
332
    }
333
334
    /**
335
     * Get custom disposable value
336
     *
337
     * @param null|bool $request
338
     * @return null|bool
339
     */
340
    public function getDisposable($request)
341
    {
342
        return $request !== null ? $request : $this->disposable;
343
    }
344
345
    /**
346
     * Set custom disposable value
347
     *
348
     * @param bool $disposable
349
     * @return $this
350
     */
351
    public function setDisposable($disposable)
352
    {
353
        $this->disposable = $disposable;
354
        return $this;
355
    }
356
357
    /**
358
     * Get custom set quantity value
359
     *
360
     * @param null|int $request
361
     * @return null|int
362
     */
363
    public function getQuantity($request)
364
    {
365
        return $request !== null ? $request : $this->quantity;
366
    }
367
368
    /**
369
     * Set custom quantity value
370
     *
371
     * @param int $quantity
372
     * @return $this
373
     */
374
    public function setQuantity($quantity)
375
    {
376
        $this->quantity = $quantity;
377
        return $this;
378
    }
379
380
    /**
381
     * Set custom prefix for next generation
382
     *
383
     * @param string $prefix
384
     * @return $this
385
     */
386
    public function setPrefix($prefix)
387
    {
388
        $this->prefix = $prefix;
389
        return $this;
390
    }
391
392
    /**
393
     * Set custom suffix for next generation
394
     *
395
     * @param string $suffix
396
     * @return $this
397
     */
398
    public function setSuffix($suffix)
399
    {
400
        $this->suffix = $suffix;
401
        return $this;
402
    }
403
404
    /**
405
     * Reedem promocode to user that it's used from now.
406
     *
407
     * @param string $code
408
     *
409
     * @return bool|Promocode
410
     * @throws AlreadyUsedException
411
     * @throws UnauthenticatedException
412
     */
413
    public function redeem($code)
414
    {
415
        return $this->apply($code);
416
    }
417
418
    /**
419
     * Apply promocode to user that it's used from now.
420
     *
421
     * @param string $code
422
     *
423
     * @return bool|Promocode
424
     * @throws AlreadyUsedException
425
     * @throws UnauthenticatedException
426
     */
427
    public function apply($code)
428
    {
429
        if (!auth()->check()) {
0 ignored issues
show
Bug introduced by
The method check does only exist in Illuminate\Contracts\Auth\Guard, but not in Illuminate\Contracts\Auth\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
430
            throw new UnauthenticatedException;
431
        }
432
433
        if ($promocode = $this->check($code)) {
434
            if ($this->isSecondUsageAttempt($promocode)) {
0 ignored issues
show
Bug introduced by
It seems like $promocode defined by $this->check($code) on line 433 can also be of type boolean; however, Gabievi\Promocodes\Promo...:isSecondUsageAttempt() does only seem to accept object<Gabievi\Promocodes\Models\Promocode>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
435
                throw new AlreadyUsedException;
436
            }
437
438
            $promocode->users()->attach(auth()->id(), [
0 ignored issues
show
Bug introduced by
The method id does only exist in Illuminate\Contracts\Auth\Guard, but not in Illuminate\Contracts\Auth\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
439
                'promocode_id' => $promocode->id,
440
                'used_at' => Carbon::now(),
441
            ]);
442
443
            if (!is_null($promocode->quantity)) {
444
                $promocode->quantity -= 1;
445
                $promocode->save();
446
            }
447
448
            return $promocode->load('users');
449
        }
450
451
        return false;
452
    }
453
454
    /**
455
     * Check promocode in database if it is valid.
456
     *
457
     * @param string $code
458
     *
459
     * @return bool|Promocode
460
     * @throws InvalidPromocodeException
461
     */
462
    public function check($code)
463
    {
464
        $promocode = Promocode::byCode($code)->first();
465
466
        if ($promocode === null || $promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists()) || $promocode->isOverAmount()) {
467
            return false;
468
        }
469
470
        return $promocode;
471
    }
472
473
    /**
474
     * Check if user is trying to apply code again.
475
     *
476
     * @param Promocode $promocode
477
     *
478
     * @return bool
479
     */
480
    public function isSecondUsageAttempt(Promocode $promocode)
481
    {
482
        return $promocode->users()->wherePivot(config('promocodes.related_pivot_key', 'user_id'),
483
            auth()->id())->exists();
0 ignored issues
show
Bug introduced by
The method id does only exist in Illuminate\Contracts\Auth\Guard, but not in Illuminate\Contracts\Auth\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
484
    }
485
486
    /**
487
     * Expire code as it won't usable anymore.
488
     *
489
     * @param string $code
490
     * @return bool
491
     * @throws InvalidPromocodeException
492
     */
493
    public function disable($code)
494
    {
495
        $promocode = Promocode::byCode($code)->first();
496
497
        if ($promocode === null) {
498
            throw new InvalidPromocodeException;
499
        }
500
501
        $promocode->expires_at = Carbon::now();
502
        $promocode->quantity = 0;
503
504
        return $promocode->save();
505
    }
506
507
    /**
508
     * Clear all expired and used promotion codes
509
     * that can not be used anymore.
510
     *
511
     * @return void
512
     */
513
    public function clearRedundant()
514
    {
515
        Promocode::all()->each(function (Promocode $promocode) {
516
            if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists()) || $promocode->isOverAmount()) {
517
                $promocode->users()->detach();
518
                $promocode->delete();
519
            }
520
        });
521
    }
522
523
    /**
524
     * Get the list of valid promocodes
525
     *
526
     * @return Promocode[]|\Illuminate\Database\Eloquent\Collection
527
     */
528
    public function all()
529
    {
530
        return Promocode::all()->filter(function (Promocode $promocode) {
531
            return !$promocode->isExpired() && !($promocode->isDisposable() && $promocode->users()->exists()) && !$promocode->isOverAmount();
532
        });
533
    }
534
}
535