Completed
Push — master ( 16f1b9...768683 )
by Zura
01:35
created

Promocodes::all()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 4
nc 1
nop 0
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
     * Generated codes will be saved here
15
     * to be validated later.
16
     *
17
     * @var array
18
     */
19
    private $codes = [];
20
21
    /**
22
     * Length of code will be calculated from asterisks you have
23
     * set as mask in your config file.
24
     *
25
     * @var int
26
     */
27
    private $length;
28
29
    /**
30
     * Promocodes constructor.
31
     */
32
    public function __construct()
33
    {
34
        $this->codes = Promocode::pluck('code')->toArray();
35
        $this->length = substr_count(config('promocodes.mask'), '*');
36
    }
37
38
    /**
39
     * Generates promocodes as many as you wish.
40
     *
41
     * @param int $amount
42
     *
43
     * @return array
44
     */
45
    public function output($amount = 1)
46
    {
47
        $collection = [];
48
49
        for ($i = 1; $i <= $amount; $i++) {
50
            $random = $this->generate();
51
52
            while (!$this->validate($collection, $random)) {
53
                $random = $this->generate();
54
            }
55
56
            array_push($collection, $random);
57
        }
58
59
        return $collection;
60
    }
61
62
    /**
63
     * Save promocodes into database
64
     * Successful insert returns generated promocodes
65
     * Fail will return empty collection.
66
     *
67
     * @param int $amount
68
     * @param null $reward
69
     * @param array $data
70
     * @param int|null $expires_in
71
     * @param bool $is_disposable
72
     * @param int|null $quantity
73
     *
74
     * @return \Illuminate\Support\Collection
75
     */
76
    public function create(
77
        $amount = 1,
78
        $reward = null,
79
        array $data = [],
80
        $expires_in = null,
81
        $quantity = null,
82
        $is_disposable = false
83
    ) {
84
        $records = [];
85
86
        foreach ($this->output($amount) as $code) {
87
            $records[] = [
88
                'code' => $code,
89
                'reward' => $reward,
90
                'data' => json_encode($data),
91
                'expires_at' => $expires_in ? Carbon::now()->addDays($expires_in) : null,
92
                'is_disposable' => $is_disposable,
93
                'quantity' => $quantity,
94
            ];
95
        }
96
97
        if (Promocode::insert($records)) {
98
            return collect($records)->map(function ($record) {
99
                $record['data'] = json_decode($record['data'], true);
100
101
                return $record;
102
            });
103
        }
104
105
        return collect([]);
106
    }
107
108
    /**
109
     * Save one-time use promocodes into database
110
     * Successful insert returns generated promocodes
111
     * Fail will return empty collection.
112
     *
113
     * @param int $amount
114
     * @param null $reward
115
     * @param array $data
116
     * @param int|null $expires_in
117
     * @param int|null $quantity
118
     *
119
     * @return \Illuminate\Support\Collection
120
     */
121
    public function createDisposable(
122
        $amount = 1,
123
        $reward = null,
124
        array $data = [],
125
        $expires_in = null,
126
        $quantity = null
127
    ) {
128
        return $this->create($amount, $reward, $data, $expires_in, $quantity, true);
129
    }
130
131
    /**
132
     * Check promocode in database if it is valid.
133
     *
134
     * @param string $code
135
     *
136
     * @return bool|Promocode
137
     * @throws InvalidPromocodeException
138
     */
139
    public function check($code)
140
    {
141
        $promocode = Promocode::byCode($code)->first();
142
143
        if ($promocode === null) {
144
            throw new InvalidPromocodeException;
145
        }
146
147
        if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists()) || $promocode->isOverAmount()) {
148
            return false;
149
        }
150
151
        return $promocode;
152
    }
153
154
    /**
155
     * Apply promocode to user that it's used from now.
156
     *
157
     * @param string $code
158
     *
159
     * @return bool|Promocode
160
     * @throws AlreadyUsedException
161
     * @throws UnauthenticatedException
162
     */
163
    public function apply($code)
164
    {
165
        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...
166
            throw new UnauthenticatedException;
167
        }
168
169
        try {
170
            if ($promocode = $this->check($code)) {
171
                if ($this->isSecondUsageAttempt($promocode)) {
0 ignored issues
show
Bug introduced by
It seems like $promocode defined by $this->check($code) on line 170 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...
172
                    throw new AlreadyUsedException;
173
                }
174
175
                $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...
176
                    'promocode_id' => $promocode->id,
177
                    'used_at' => Carbon::now(),
178
                ]);
179
180
                if (!is_null($promocode->quantity)) {
181
                    $promocode->quantity -= 1;
182
                    $promocode->save();
183
                }
184
185
                return $promocode->load('users');
186
            }
187
        } catch (InvalidPromocodeException $exception) {
188
            //
189
        }
190
191
        return false;
192
    }
193
194
    /**
195
     * Reedem promocode to user that it's used from now.
196
     *
197
     * @param string $code
198
     *
199
     * @return bool|Promocode
200
     * @throws AlreadyUsedException
201
     * @throws UnauthenticatedException
202
     */
203
    public function redeem($code)
204
    {
205
        return $this->apply($code);
206
    }
207
208
    /**
209
     * Expire code as it won't usable anymore.
210
     *
211
     * @param string $code
212
     * @return bool
213
     * @throws InvalidPromocodeException
214
     */
215
    public function disable($code)
216
    {
217
        $promocode = Promocode::byCode($code)->first();
218
219
        if ($promocode === null) {
220
            throw new InvalidPromocodeException;
221
        }
222
223
        $promocode->expires_at = Carbon::now();
224
        $promocode->quantity = 0;
225
226
        return $promocode->save();
227
    }
228
229
    /**
230
     * Clear all expired and used promotion codes
231
     * that can not be used anymore.
232
     *
233
     * @return void
234
     */
235
    public function clearRedundant()
236
    {
237
        Promocode::all()->each(function (Promocode $promocode) {
238
            if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists()) || $promocode->isOverAmount()) {
239
                $promocode->users()->detach();
240
                $promocode->delete();
241
            }
242
        });
243
    }
244
245
    /**
246
     * Get the list of valid promocodes
247
     *
248
     * @return Promocode[]|\Illuminate\Database\Eloquent\Collection
249
     */
250
    public function all()
251
    {
252
        return Promocode::all()->filter(function (Promocode $promocode) {
253
            return !$promocode->isExpired() && !($promocode->isDisposable() && $promocode->users()->exists()) && !$promocode->isOverAmount();
254
        });
255
    }
256
257
    /**
258
     * Here will be generated single code using your parameters from config.
259
     *
260
     * @return string
261
     */
262
    private function generate()
263
    {
264
        $characters = config('promocodes.characters');
265
        $mask = config('promocodes.mask');
266
        $promocode = '';
267
        $random = [];
268
269
        for ($i = 1; $i <= $this->length; $i++) {
270
            $character = $characters[rand(0, strlen($characters) - 1)];
271
            $random[] = $character;
272
        }
273
274
        shuffle($random);
275
        $length = count($random);
276
277
        $promocode .= $this->getPrefix();
278
279
        for ($i = 0; $i < $length; $i++) {
280
            $mask = preg_replace('/\*/', $random[$i], $mask, 1);
281
        }
282
283
        $promocode .= $mask;
284
        $promocode .= $this->getSuffix();
285
286
        return $promocode;
287
    }
288
289
    /**
290
     * Generate prefix with separator for promocode.
291
     *
292
     * @return string
293
     */
294
    private function getPrefix()
295
    {
296
        return (bool)config('promocodes.prefix')
297
            ? config('promocodes.prefix') . config('promocodes.separator')
298
            : '';
299
    }
300
301
    /**
302
     * Generate suffix with separator for promocode.
303
     *
304
     * @return string
305
     */
306
    private function getSuffix()
307
    {
308
        return (bool)config('promocodes.suffix')
309
            ? config('promocodes.separator') . config('promocodes.suffix')
310
            : '';
311
    }
312
313
    /**
314
     * Your code will be validated to be unique for one request.
315
     *
316
     * @param $collection
317
     * @param $new
318
     *
319
     * @return bool
320
     */
321
    private function validate($collection, $new)
322
    {
323
        return !in_array($new, array_merge($collection, $this->codes));
324
    }
325
326
    /**
327
     * Check if user is trying to apply code again.
328
     *
329
     * @param Promocode $promocode
330
     *
331
     * @return bool
332
     */
333
    public function isSecondUsageAttempt(Promocode $promocode)
334
    {
335
        return $promocode->users()->wherePivot(config('promocodes.related_pivot_key', 'user_id'),
336
            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...
337
    }
338
}
339