Completed
Pull Request — master (#70)
by
unknown
01:31
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 Carbon|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 Carbon|null $expires_in
135
     * @param int|null $quantity
136
     * @param bool $is_disposable
137
     * @param array $custom_codes
138
     *
139
     * @return \Illuminate\Support\Collection
140
     */
141
    public function create(
142
        $amount = null,
143
        $reward = null,
144
        $data = null,
145
        $expires_in = null,
146
        $quantity = null,
147
        $is_disposable = null,
148
        $custom_codes = []
149
    )
150
    {
151
        $records = [];
152
153
        $codes = count($custom_codes) > 0 ? $custom_codes : $this->output($amount);
154
155
        foreach ($codes as $code) {
156
            $records[] = [
157
                'code' => $code,
158
                'reward' => $this->getReward($reward),
159
                'data' => json_encode($this->getData($data)),
160
                'expires_at' => $expires_in === null ? Carbon::tomorrow()->endOfDay() : $expires_in,
0 ignored issues
show
Bug introduced by
The method endOfDay() does not exist on Carbon\Traits\Comparison. Did you maybe mean isEndOfDay()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
161
                'is_disposable' => $this->getDisposable($is_disposable),
162
                'quantity' => $this->getQuantity($quantity),
163
            ];
164
        }
165
166
        if (Promocode::insert($records)) {
167
            return collect($records)->map(function ($record) {
168
                $record['data'] = json_decode($record['data'], true);
169
170
                return $record;
171
            });
172
        }
173
174
        return collect([]);
175
    }
176
177
    /**
178
     * Generates promocodes as many as you wish.
179
     *
180
     * @param int $amount
181
     *
182
     * @return array
183
     */
184
    public function output($amount = null)
185
    {
186
        $collection = [];
187
188
        for ($i = 1; $i <= $this->getAmount($amount); $i++) {
189
            $random = $this->generate();
190
191
            while (!$this->validate($collection, $random)) {
192
                $random = $this->generate();
193
            }
194
195
            array_push($collection, $random);
196
        }
197
198
        return $collection;
199
    }
200
201
    /**
202
     * Get number of codes to be generated
203
     *
204
     * @param null|int $request
205
     * @return null|int
206
     */
207
    public function getAmount($request)
208
    {
209
        return $request !== null ? $request : $this->amount;
210
    }
211
212
    /**
213
     * Set how much code you want to be generated
214
     *
215
     * @param int $amount
216
     * @return $this
217
     */
218
    public function setAmount($amount)
219
    {
220
        $this->amount = $amount;
221
        return $this;
222
    }
223
224
    /**
225
     * Here will be generated single code using your parameters from config.
226
     *
227
     * @return string
228
     */
229
    private function generate()
230
    {
231
        $characters = config('promocodes.characters');
232
        $mask = config('promocodes.mask');
233
        $promocode = '';
234
        $random = [];
235
236
        for ($i = 1; $i <= $this->length; $i++) {
237
            $character = $characters[rand(0, strlen($characters) - 1)];
238
            $random[] = $character;
239
        }
240
241
        shuffle($random);
242
        $length = count($random);
243
244
        $promocode .= $this->prefix;
245
246
        for ($i = 0; $i < $length; $i++) {
247
            $mask = preg_replace('/\*/', $random[$i], $mask, 1);
248
        }
249
250
        $promocode .= $mask;
251
        $promocode .= $this->suffix;
252
253
        return $promocode;
254
    }
255
256
    /**
257
     * Your code will be validated to be unique for one request.
258
     *
259
     * @param $collection
260
     * @param $new
261
     *
262
     * @return bool
263
     */
264
    private function validate($collection, $new)
265
    {
266
        return !in_array($new, array_merge($collection, $this->codes));
267
    }
268
269
    /**
270
     * Get custom set reward value
271
     *
272
     * @param null|int $request
273
     * @return null|int
274
     */
275
    public function getReward($request)
276
    {
277
        return $request !== null ? $request : $this->reward;
278
    }
279
280
    /**
281
     * Set custom reward value
282
     *
283
     * @param int $reward
284
     * @return $this
285
     */
286
    public function setReward($reward)
287
    {
288
        $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...
289
        return $this;
290
    }
291
292
    /**
293
     * Get custom set data value
294
     *
295
     * @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...
296
     * @return null|array
297
     */
298
    public function getData($request)
299
    {
300
        return $request !== null ? $request : $this->data;
301
    }
302
303
    /**
304
     * Set custom data value
305
     *
306
     * @param array $data
307
     * @return $this
308
     */
309
    public function setData($data)
310
    {
311
        $this->data = $data;
312
        return $this;
313
    }
314
315
    /**
316
     * Get custom disposable value
317
     *
318
     * @param null|bool $request
319
     * @return null|bool
320
     */
321
    public function getDisposable($request)
322
    {
323
        return $request !== null ? $request : $this->disposable;
324
    }
325
326
    /**
327
     * Set custom disposable value
328
     *
329
     * @param bool $disposable
330
     * @return $this
331
     */
332
    public function setDisposable($disposable = true)
333
    {
334
        $this->disposable = $disposable;
335
        return $this;
336
    }
337
338
    /**
339
     * Get custom set quantity value
340
     *
341
     * @param null|int $request
342
     * @return null|int
343
     */
344
    public function getQuantity($request)
345
    {
346
        return $request !== null ? $request : $this->quantity;
347
    }
348
349
    /**
350
     * Set custom quantity value
351
     *
352
     * @param int $quantity
353
     * @return $this
354
     */
355
    public function setQuantity($quantity)
356
    {
357
        $this->quantity = $quantity;
358
        return $this;
359
    }
360
361
    /**
362
     * Set custom prefix for next generation
363
     *
364
     * @param string $prefix
365
     * @return $this
366
     */
367
    public function setPrefix($prefix)
368
    {
369
        $this->prefix = $prefix;
370
        return $this;
371
    }
372
373
    /**
374
     * Set custom suffix for next generation
375
     *
376
     * @param string $suffix
377
     * @return $this
378
     */
379
    public function setSuffix($suffix)
380
    {
381
        $this->suffix = $suffix;
382
        return $this;
383
    }
384
385
    /**
386
     * Reedem promocode to user that it's used from now.
387
     *
388
     * @param string $code
389
     *
390
     * @return bool|Promocode
391
     * @throws AlreadyUsedException
392
     * @throws UnauthenticatedException
393
     */
394
    public function redeem($code)
395
    {
396
        return $this->apply($code);
397
    }
398
399
    /**
400
     * Apply promocode to user that it's used from now.
401
     *
402
     * @param string $code
403
     *
404
     * @return bool|Promocode
405
     * @throws AlreadyUsedException
406
     * @throws UnauthenticatedException
407
     */
408
    public function apply($code)
409
    {
410
        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...
411
            throw new UnauthenticatedException;
412
        }
413
414
        if ($promocode = $this->check($code)) {
415
            if ($this->isSecondUsageAttempt($promocode)) {
0 ignored issues
show
Bug introduced by
It seems like $promocode defined by $this->check($code) on line 414 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...
416
                throw new AlreadyUsedException;
417
            }
418
419
            $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...
420
                'promocode_id' => $promocode->id,
421
                'used_at' => Carbon::now(),
422
            ]);
423
424
            if (!is_null($promocode->quantity)) {
425
                $promocode->quantity -= 1;
426
                $promocode->save();
427
            }
428
429
            return $promocode->load('users');
430
        }
431
432
        return false;
433
    }
434
435
    /**
436
     * Check promocode in database if it is valid.
437
     *
438
     * @param string $code
439
     *
440
     * @return bool|Promocode
441
     */
442
    public function check($code)
443
    {
444
        $promocode = Promocode::byCode($code)->first();
445
446
        if ($promocode === null || $promocode->isExpired()) {
447
            return false;
448
        }
449
450
        return $promocode;
451
    }
452
453
    /**
454
     * Check if user is trying to apply code again.
455
     *
456
     * @param Promocode $promocode
457
     *
458
     * @return bool
459
     */
460
    public function isSecondUsageAttempt(Promocode $promocode)
461
    {
462
        return $promocode->isDisposable() && $promocode->users()->wherePivot(config('promocodes.related_pivot_key', 'user_id'),
463
                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...
464
    }
465
466
    /**
467
     * Expire code as it won't be usable anymore.
468
     *
469
     * @param string $code
470
     * @return bool
471
     * @throws InvalidPromocodeException
472
     */
473
    public function disable($code)
474
    {
475
        $promocode = Promocode::byCode($code)->first();
476
477
        if ($promocode === null) {
478
            throw new InvalidPromocodeException;
479
        }
480
481
        $promocode->expires_at = Carbon::now();
482
        $promocode->quantity = 0;
483
484
        return $promocode->save();
485
    }
486
487
    /**
488
     * Clear all expired and used promotion codes
489
     * that can not be used anymore.
490
     *
491
     * @return void
492
     */
493
    public function clearRedundant()
494
    {
495
        Promocode::all()->each(function (Promocode $promocode) {
496
            if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists()) || $promocode->isOverAmount()) {
497
                $promocode->users()->detach();
498
                $promocode->delete();
499
            }
500
        });
501
    }
502
503
    /**
504
     * Get the list of valid promocodes
505
     *
506
     * @return Promocode[]|\Illuminate\Database\Eloquent\Collection
507
     */
508
    public function all()
509
    {
510
        return Promocode::all()->filter(function (Promocode $promocode) {
511
            return !$promocode->isExpired() && !($promocode->isDisposable() && $promocode->users()->exists()) && !$promocode->isOverAmount();
512
        });
513
    }
514
}
515