-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCouponCodeGenerator.php
More file actions
380 lines (336 loc) · 10.7 KB
/
CouponCodeGenerator.php
File metadata and controls
380 lines (336 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
#!/usr/bin/env php
<?php
use bashedev\CouponCodeGenerator\CouponCode;
/**
*
* Copyright (C) 2016 Bashe Development
*
* All rights reserved.
*
* Created on : Aug 01, 2016, 23:26
* Author : Joe Bashe <joe@bashedev.com>
*
*/
namespace bashedev\CouponCodeGenerator;
/**
* CouponCodeGenerator
*
* @author Joe Bashe <joe@bashedev.com>
*/
class CouponCodeGenerator
{
const COUPON_CODE_LENGTH = 9; // not including spacer(s)
const COUPON_CODE_GROUP_SIZE = 3;
const COUPON_CODE_SPACER = '-';
/**
*
* @var array All possible characters to be used in each position of access code
*/
private $characterSet = [];
/**
*
* @var int Variable to store the character set length
*/
private $characterSetCount = 0;
/**
*
* @var array Existing invitation Codes in database
*/
private $existingCodes = [];
/**
*
* @var int max numeric (ordinal) value of digit: ($this->characterSetLength - 1)
*/
private $maxDigitValue = 0;
/**
*
* @var int The maximum increment that should be used based upon possible quantity of permutations and quantity of
* codes desired.
*/
private $maxIncrement = 0;
/**
*
* @var int The maximum exponent of the increment in terms of base {$this->characterSetLength}
*/
private $maxExponent = 0;
/**
*
* @var string Output type (write "file" or "db")
*/
private $output = 'file';
/**
*
* @var array Keeps track of current position in character set arrays
*/
private $positionCounter = [];
/**
*
* @var int Quantity of digits in code
*/
private $qtyDigits = 0;
/**
*
* @var int Quantity of possible permutations
*/
private $qtyPermutations = 0;
/**
*
* @var int Quantity of codes to generate
*/
private $qtyToGenerate = 0;
/**
*
* @var array
*/
private $randomCharacterSets = [];
/**
* Top level function to generate invite codes
*
* @param int $qtyToGenerate Quantity of invite codes to generate
* @param string $output Output type to generate (write file or db)
* @param string $filename
* @param float $discount Discount percentage expressed as decimal, e.g. 0.50 for 50%
*/
public function generateCouponCodes($qtyToGenerate, $output = 'file', $filename = 'new-codes.csv', $discount = null)
{
$this->output = $output;
$this->qtyToGenerate = $qtyToGenerate;
$this->init();
if ($output === 'file') {
$qtyCodes = $this->createInvitations($discount, $filename);
} else {
$qtyCodes = $this->createInvitations($discount);
}
$sparsity = (1 - ($qtyCodes / $this->qtyPermutations)) * 100;
printf(
"Generated %s access codes from a total set of %s (%s%% sparsity)\n",
number_format($qtyCodes),
number_format($this->qtyPermutations),
number_format($sparsity, 6)
);
}
/**
*
* @param array $increment
*
* @throws \Exception
*/
private function addIncrement($increment)
{
$carry = false;
foreach ($increment as $exponent => $multiplier) {
if (array_key_exists($exponent, $this->positionCounter)) {
$currentValue = $this->positionCounter[$exponent];
} else {
throw new \Exception("Exponent ($exponent) doesn't exist: \n".print_r($this->positionCounter, true));
}
if ($carry === false) {
$sum = $currentValue + $multiplier;
} else {
$sum = $currentValue + $carry + $multiplier;
$carry = false;
}
if ($sum < $this->characterSetCount) {
// fits in bounds, continue adding the next digit
$this->positionCounter[$exponent] = $sum;
} else { // carry
$sum -= $this->characterSetCount;
$carry = 1;
$this->positionCounter[$exponent] = $sum;
}
}
}
/**
*
* @param int $number
*
* @return array
*/
private function baseConvert($number)
{
// now descend thru powers until number is converted
$power = $this->maxExponent;
$converted = array_fill(0, $power, 0);
$carry = $number;
while ($carry > 0 && $power >= 0) {
$multiplier = pow($this->characterSetCount, $power);
for ($ordinal = $this->maxDigitValue; $ordinal > 0; $ordinal--) {
$difference = $carry - ($ordinal * $multiplier);
if ($difference < 0) { // took too much, try a lower ordinal
continue;
}
// took away something, good. mark it and set the carry to the difference, then continue on to the next
// power
$converted[$power] = $ordinal;
$carry = $difference;
break;
}
$power--;
}
return $converted;
}
/**
*
* @param string $code
* @param float $discount
*
* @return CouponCode
*/
private function constructCouponCode($code, $discount)
{
$couponCode = new CouponCode();
$couponCode->setCode($code);
$couponCode->setCustomerId(0); // Customer ID = 0 => (not set)
$couponCode->setDiscount($discount);
$couponCode->setDiscountTarget('RETAIL');
return $couponCode;
}
/**
*
* @param float $discount
* @param string $filename
*
* @return int
*/
private function createInvitations($discount = null, $filename = null)
{
$codes = [];
$qtyCodes = 0;
while ($qtyCodes < $this->qtyToGenerate) {
$code = $this->getCode();
if (in_array($code, $this->existingCodes)) { // skip existing code
continue;
}
if ($this->output === 'file') { // generate csv file
$codes[] = $code;
} else { // save to db
// save to db, e.g. $this->entityManager->persist($this->constructCouponCode($code, $discount));
}
$qtyCodes++;
}
if ($this->output === 'file') {
$this->writeFile($codes, $filename);
$this->existingCodes = array_merge($this->existingCodes, $codes);
} else { // write db
// write db, e.g. $this->entityManager->flush();
}
return $qtyCodes;
}
/**
* Creates the randomized character sets to be used to generate permutations
*/
private function createRandomCharacterSets()
{
// build an array of randomized character arrays
for ($i = 0; $i < self::COUPON_CODE_LENGTH; $i++) {
shuffle($this->characterSet);
$this->randomCharacterSets[] = $this->characterSet;
}
shuffle($this->randomCharacterSets);
}
/**
* @param string $permutation
*
* @return string
*/
private function formatPermutation($permutation)
{
return implode(self::COUPON_CODE_SPACER, str_split($permutation, self::COUPON_CODE_GROUP_SIZE));
}
/**
* Increment the positional counter (i.e. $this->pos) and get a single code.
*
* @return string Coupon code
*/
private function getCode()
{
$this->incrementPermutationCounterRandomly();
$permutation = $this->getCurrentPermutation();
return $this->formatPermutation($permutation);
}
/**
* Builds an invitation code out of the current permutation
*
* @return string Gets the current permutation (a.k.a code) by iterating through the $this->pos position-tracking
* array over the $this->randomCharacterSets array
*/
private function getCurrentPermutation()
{
$code = '';
foreach ($this->positionCounter as $col => $row) {
$code = $this->randomCharacterSets[$col][$row].$code;
}
return $code;
}
/**
* Increment the permutation counter by random quantity
*
* @throws \Exception
*/
private function incrementPermutationCounterRandomly()
{
$increment = rand(1, $this->maxIncrement);
$convertedIncrement = $this->baseConvert($increment);
$this->addIncrement($convertedIncrement);
}
/**
* Initialize class members
*/
private function init()
{
// define alphanumeric character set, minus easily confusable letters and numbers (0, 1, I, O)
$this->characterSet = array_merge(range('A', 'H'), range('J', 'N'), range('P', 'Z'), range(2, 9));
$this->characterSetCount = count($this->characterSet);
// 0-indexed, so 0 = A, 1 = B, ... where the numbers are indexes and letters are members of $this->characterSet
$this->maxDigitValue = $this->characterSetCount - 1;
$this->qtyDigits = self::COUPON_CODE_LENGTH;
$this->qtyPermutations = pow($this->characterSetCount, $this->qtyDigits); // (qty_chars)^(qty_digits)
$this->maxIncrement = round($this->qtyPermutations / pow($this->qtyToGenerate, 0.25));
$this->positionCounter = array_fill(0, $this->qtyDigits, 0);
while (pow($this->characterSetCount, $this->maxExponent) < $this->maxIncrement) {
$this->maxExponent++;
}
$this->createRandomCharacterSets();
$this->loadExistingCodes();
}
/**
* Load existing codes into class variable
*/
private function loadExistingCodes()
{
// write code to load any existing coupon codes to ensure no duplicates are created, e.g.:
//$existingCoupons = $this->entityManager->getRepository('AppBundle:CouponCode')->findAll();
//$this->existingCodes = array_map(
// function (CouponCode $value) {
// return $value->getCode();
// },
// $existingCoupons
//);
$filename = 'existing-codes.csv';
if (file_exists($filename)) {
$this->existingCodes = explode("\n", file_get_contents($filename));
}
}
/**
* Shuffles the array of codes and writes them to a CSV file (one per line).
*
* @param array $codes
* @param string $filename
*/
private function writeFile(array $codes, $filename)
{
shuffle($codes);
file_put_contents(
$filename,
array_reduce(
$codes,
function ($carry, $item) {
return $carry."$item\n";
}
)
);
}
}
$ccg = new CouponCodeGenerator();
$filename = 'coupon_codes/codes.csv';
$ccg->generateCouponCodes(1000, 'file', $filename);