Completed
Push — master ( 7ee247...f51536 )
by Zura
01:48
created

Promocodes::createDisposable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 4
1
<?php
2
3
namespace Gabievi\Promocodes;
4
5
use Carbon\Carbon;
6
use Gabievi\Promocodes\Model\Promocode;
7
use Gabievi\Promocodes\Exceptions\AlreadyUsedExceprion;
8
use Gabievi\Promocodes\Exceptions\UnauthenticatedExceprion;
9
use Gabievi\Promocodes\Exceptions\InvalidPromocodeExceprion;
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
36
        $this->length = substr_count(config('promocodes.mask'), '*');
37
    }
38
39
    /**
40
     * Generates promocodes as many as you wish.
41
     *
42
     * @param int $amount
43
     *
44
     * @return array
45
     */
46
    public function output($amount = 1)
47
    {
48
        $collection = [];
49
50
        for ($i = 1; $i <= $amount; $i++) {
51
            $random = $this->generate();
52
53
            while (!$this->validate($collection, $random)) {
54
                $random = $this->generate();
55
            }
56
57
            array_push($collection, $random);
58
        }
59
60
        return $collection;
61
    }
62
63
    /**
64
     * Save promocodes into database
65
     * Successful insert returns generated promocodes
66
     * Fail will return empty collection.
67
     *
68
     * @param int $amount
69
     * @param null $reward
70
     * @param array $data
71
     * @param int|null $expires_in
72
     * @param bool $is_disposable
73
     *
74
     * @return \Illuminate\Support\Collection
75
     */
76
    public function create($amount = 1, $reward = null, array $data = [], $expires_in = null, $is_disposable = false)
77
    {
78
        $records = [];
79
80
        foreach ($this->output($amount) as $code) {
81
            $records[] = [
82
                'code' => $code,
83
                'reward' => $reward,
84
                'data' => json_encode($data),
85
                'expires_at' => $expires_in ? Carbon::now()->addDays($expires_in) : null,
86
                'is_disposable' => $is_disposable,
87
            ];
88
        }
89
90
        if (Promocode::insert($records)) {
0 ignored issues
show
Bug introduced by
The method insert() does not exist on Gabievi\Promocodes\Model\Promocode. Did you maybe mean performInsert()?

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...
91
            return collect($records);
92
        }
93
94
        return collect([]);
95
    }
96
97
    /**
98
     * Save one-time use promocodes into database
99
     * Successful insert returns generated promocodes
100
     * Fail will return empty collection.
101
     *
102
     * @param int $amount
103
     * @param null $reward
104
     * @param array $data
105
     * @param int|null $expires_in
106
     *
107
     * @return \Illuminate\Support\Collection
108
     */
109
    public function createDisposable($amount = 1, $reward = null, array $data = [], $expires_in = null)
110
    {
111
        return $this->create($amount, $reward, $data, $expires_in, true);
112
    }
113
114
    /**
115
     * Check promocode in database if it is valid.
116
     *
117
     * @param string $code
118
     *
119
     * @return bool|\Gabievi\Promocodes\Model\Promocode
120
     * @throws \Gabievi\Promocodes\Exceptions\InvalidPromocodeExceprion
121
     */
122
    public function check($code)
123
    {
124
        $promocode = Promocode::byCode($code)->first();
0 ignored issues
show
Bug introduced by
The method byCode() does not exist on Gabievi\Promocodes\Model\Promocode. Did you maybe mean scopeByCode()?

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...
125
126
        if ($promocode === null) {
127
            throw new InvalidPromocodeExceprion;
128
        }
129
130
        if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists())) {
131
            return false;
132
        }
133
134
        return $promocode;
135
    }
136
137
    /**
138
     * Apply promocode to user that it's used from now.
139
     *
140
     * @param string $code
141
     *
142
     * @return bool|\Gabievi\Promocodes\Model\Promocode
143
     * @throws \Gabievi\Promocodes\Exceptions\UnauthenticatedExceprion|\Gabievi\Promocodes\Exceptions\AlreadyUsedExceprion
144
     */
145
    public function apply($code)
146
    {
147
        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...
148
            throw new UnauthenticatedExceprion;
149
        }
150
151
        if ($promocode = $this->check($code)) {
152
            if ($this->isSecondUsageAttempt($promocode)) {
153
                throw new AlreadyUsedExceprion;
154
            }
155
156
            $promocode->users()->attach(auth()->user()->id, [
0 ignored issues
show
Bug introduced by
The method user 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...
157
                'used_at' => Carbon::now(),
158
            ]);
159
160
            return $promocode->load('users');
161
        }
162
163
        return false;
164
    }
165
166
    /**
167
     * Expire code as it won't usable anymore.
168
     *
169
     * @param string $code
170
     * @return bool
171
     * @throws \Gabievi\Promocodes\Exceptions\InvalidPromocodeExceprion
172
     */
173
    public function disable($code)
174
    {
175
        $promocode = Promocode::byCode($code)->first();
0 ignored issues
show
Bug introduced by
The method byCode() does not exist on Gabievi\Promocodes\Model\Promocode. Did you maybe mean scopeByCode()?

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...
176
177
        if ($promocode === null) {
178
            throw new InvalidPromocodeExceprion;
179
        }
180
181
        $promocode->expires_at = Carbon::now();
182
183
        return $promocode->save();
184
    }
185
186
    /**
187
     * Clear all expired and used promotion codes
188
     * that can not be used anymore.
189
     *
190
     * @return void
191
     */
192
    public function clearRedundant()
193
    {
194
        Promocode::all()->each(function ($promocode) {
195
            if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists())) {
196
                $promocode->users()->detach();
197
                $promocode->delete();
198
            }
199
        });
200
    }
201
202
    /**
203
     * Here will be generated single code using your parameters from config.
204
     *
205
     * @return string
206
     */
207
    private function generate()
208
    {
209
        $characters = config('promocodes.characters');
210
        $mask = config('promocodes.mask');
211
        $promocode = '';
212
        $random = [];
213
214
        // take needed length of string from characters and randomize it
215
        for ($i = 1; $i <= $this->length; $i++) {
216
            $character = $characters[rand(0, strlen($characters) - 1)];
217
            $random[] = $character;
218
        }
219
220
        // shuffle randomized characters
221
        shuffle($random);
222
223
        // set prefix for promocode
224
        $promocode .= $this->getPrefix();
225
226
        // loop through asterisks and change with random symbol
227
        for ($i = 0; $i < count($random); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
228
            $mask = preg_replace('/\*/', $random[$i], $mask, 1);
229
        }
230
231
        // set updated mask as code
232
        $promocode .= $mask;
233
234
        // set suffix for promocode
235
        $promocode .= $this->getSuffix();
236
237
        return $promocode;
238
    }
239
240
    /**
241
     * Generate prefix with separator for promocode.
242
     *
243
     * @return string
244
     */
245
    private function getPrefix()
246
    {
247
        return (bool)config('promocodes.prefix')
248
            ? config('promocodes.prefix') . config('promocodes.separator')
249
            : '';
250
    }
251
252
    /**
253
     * Generate suffix with separator for promocode.
254
     *
255
     * @return string
256
     */
257
    private function getSuffix()
258
    {
259
        return (bool)config('promocodes.suffix')
260
            ? config('promocodes.separator') . config('promocodes.suffix')
261
            : '';
262
    }
263
264
    /**
265
     * Your code will be validated to be unique for one request.
266
     *
267
     * @param $collection
268
     * @param $new
269
     *
270
     * @return bool
271
     */
272
    private function validate($collection, $new)
273
    {
274
        return !in_array($new, array_merge($collection, $this->codes));
275
    }
276
277
    /**
278
     * Check if user is trying to apply code again.
279
     *
280
     * @param $promocode
281
     *
282
     * @return bool
283
     */
284
    private function isSecondUsageAttempt($promocode) {
285
        return $promocode->users()->wherePivot('user_id', auth()->user()->id)->exists();
0 ignored issues
show
Bug introduced by
The method user 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...
286
    }
287
}
288