Completed
Push — master ( 76cc03...7ee247 )
by Zura
08:07
created

Promocodes::isSecondUsageAttempt()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 1
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 NULL.
67
     *
68
     * @param int $amount
69
     * @param null $reward
70
     * @param array $data
71
     * @param int|null $expires_in
72
     *
73
     * @return \Illuminate\Support\Collection
74
     */
75
    public function create($amount = 1, $reward = null, array $data = [], $expires_in = null)
76
    {
77
        $records = [];
78
79
        foreach ($this->output($amount) as $code) {
80
            $records[] = [
81
                'code' => $code,
82
                'reward' => $reward,
83
                'data' => json_encode($data),
84
                'expires_at' => $expires_in ? Carbon::now()->addDays($expires_in) : null,
85
            ];
86
        }
87
88
        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...
89
            return collect($records);
90
        }
91
92
        return collect([]);
93
    }
94
95
    /**
96
     * Check promocode in database if it is valid.
97
     *
98
     * @param string $code
99
     *
100
     * @return bool|\Gabievi\Promocodes\Model\Promocode
101
     * @throws \Gabievi\Promocodes\Exceptions\InvalidPromocodeExceprion
102
     */
103
    public function check($code)
104
    {
105
        $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...
106
107
        if ($promocode === null) {
108
            throw new InvalidPromocodeExceprion;
109
        }
110
111
        if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists())) {
112
            return false;
113
        }
114
115
        return $promocode;
116
    }
117
118
    /**
119
     * Apply promocode to user that it's used from now.
120
     *
121
     * @param string $code
122
     *
123
     * @return bool|\Gabievi\Promocodes\Model\Promocode
124
     * @throws \Gabievi\Promocodes\Exceptions\UnauthenticatedExceprion|\Gabievi\Promocodes\Exceptions\AlreadyUsedExceprion
125
     */
126
    public function apply($code)
127
    {
128
        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...
129
            throw new UnauthenticatedExceprion;
130
        }
131
132
        if ($promocode = $this->check($code)) {
133
            if ($this->isSecondUsageAttempt($promocode)) {
134
                throw new AlreadyUsedExceprion;
135
            }
136
137
            $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...
138
                'used_at' => Carbon::now(),
139
            ]);
140
141
            return $promocode->load('users');
142
        }
143
144
        return false;
145
    }
146
147
    /**
148
     * Expire code as it won't usable anymore.
149
     *
150
     * @param string $code
151
     * @return bool
152
     * @throws \Gabievi\Promocodes\Exceptions\InvalidPromocodeExceprion
153
     */
154
    public function disable($code)
155
    {
156
        $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...
157
158
        if ($promocode === null) {
159
            throw new InvalidPromocodeExceprion;
160
        }
161
162
        $promocode->expires_at = Carbon::now();
163
164
        return $promocode->save();
165
    }
166
167
    /**
168
     * Clear all expired and used promotion codes
169
     * that can not be used anymore.
170
     *
171
     * @return void
172
     */
173
    public function clearRedundant()
174
    {
175
        Promocode::all()->each(function ($promocode) {
176
            if ($promocode->isExpired() || ($promocode->isDisposable() && $promocode->users()->exists())) {
177
                $promocode->users()->detach();
178
                $promocode->delete();
179
            }
180
        });
181
    }
182
183
    /**
184
     * Here will be generated single code using your parameters from config.
185
     *
186
     * @return string
187
     */
188
    private function generate()
189
    {
190
        $characters = config('promocodes.characters');
191
        $mask = config('promocodes.mask');
192
        $promocode = '';
193
        $random = [];
194
195
        // take needed length of string from characters and randomize it
196
        for ($i = 1; $i <= $this->length; $i++) {
197
            $character = $characters[rand(0, strlen($characters) - 1)];
198
            $random[] = $character;
199
        }
200
201
        // shuffle randomized characters
202
        shuffle($random);
203
204
        // set prefix for promocode
205
        $promocode .= $this->getPrefix();
206
207
        // loop through asterisks and change with random symbol
208
        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...
209
            $mask = preg_replace('/\*/', $random[$i], $mask, 1);
210
        }
211
212
        // set updated mask as code
213
        $promocode .= $mask;
214
215
        // set suffix for promocode
216
        $promocode .= $this->getSuffix();
217
218
        return $promocode;
219
    }
220
221
    /**
222
     * Generate prefix with separator for promocode.
223
     *
224
     * @return string
225
     */
226
    private function getPrefix()
227
    {
228
        return (bool)config('promocodes.prefix')
229
            ? config('promocodes.prefix') . config('promocodes.separator')
230
            : '';
231
    }
232
233
    /**
234
     * Generate suffix with separator for promocode.
235
     *
236
     * @return string
237
     */
238
    private function getSuffix()
239
    {
240
        return (bool)config('promocodes.suffix')
241
            ? config('promocodes.separator') . config('promocodes.suffix')
242
            : '';
243
    }
244
245
    /**
246
     * Your code will be validated to be unique for one request.
247
     *
248
     * @param $collection
249
     * @param $new
250
     *
251
     * @return bool
252
     */
253
    private function validate($collection, $new)
254
    {
255
        return !in_array($new, array_merge($collection, $this->codes));
256
    }
257
258
    /**
259
     * Check if user is trying to apply code again.
260
     *
261
     * @param $promocode
262
     *
263
     * @return bool
264
     */
265
    private function isSecondUsageAttempt($promocode) {
266
        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...
267
    }
268
}
269