I am developing a event organization website. Here when the user registers for an event he will be given a unique random number(10 digit), which we use to generate a barcode
One Solution could be like this:
use Illuminate\Support\Facades\Validator;
private function genUserCode(){
$this->user_code = [
'user_code' => mt_rand(1000000000,9999999999)
];
$rules = ['user_code' => 'unique:users'];
$validate = Validator::make($this->user_code, $rules)->passes();
return $validate ? $this->user_code['user_code'] : $this->genUserCode();
}
Its generating a random number between 1000000000 and 9999999999. After that, it validates the number against the table. If true then it returns the number, otherwise runs the function again.
<?php
declare(strict_types=1);
namespace App\Helpers;
use App\Exceptions\GeneratorException;
class GeneratorHelper
{
public static $limitIterations = 100000;
/**
* @param string $column
* @param string $modelClass
* @return string
* @throws GeneratorException
*/
public static function generateID(string $modelClass, string $column): string
{
return self::run(
$modelClass,
$column,
self::IDGenerator(),
'Generation id is failed. The loop limit exceeds ' . self::$limitIterations
);
}
/**
* @param string $modelClass
* @param string $column
* @param \Generator $generator
* @param string $exceptionMessage
* @param array $whereParams
* @return string
* @throws GeneratorException
*/
protected static function run(string $modelClass, string $column, \Generator $generator, string $exceptionMessage, array $whereParams = []): string
{
try {
foreach ($generator as $id) {
$query = $modelClass::where([$column => $id]);
foreach ($whereParams as $param) {
$query->where(...$param);
}
if (!$query->first()) {
return $id;
}
}
} catch (\Throwable $e) {
$exceptionMessage = $e->getMessage();
}
throw new GeneratorException($exceptionMessage);
}
protected static function IDGenerator(): ?\Generator
{
for ($i = 1; $i <= self::$limitIterations; $i++) {
yield (string)random_int(1000000000, 9999999999);
}
return null;
}
}
sample usage
$card->number = GeneratorHelper::generateID(Card::class, 'number');
You can use php's uniqid() function to generate a unique ID based on the microtime (current time in microseconds)
Example:
<?php
echo uniqid();
?>
Output:
56c3096338cdb
Looping through the array won't be that efficient. If your database becomes too large then it slow down the entire process and also there might be a rare situation when 2 threads are looping through the array for the same random number and it will be found available and return same number to both the tickets.
So instead of looping through the array you can set the 10 digit registration id as primary key and instead of looping through the array you can insert the registration details along with randomly generated number, if the database insert operation is successful you can return the registration id but if not then regenerate the random number and insert.
Alternate solution which will be more effective Instead of 10 digit random numbers you can use timestamp to generate a 10 digit unique registration number and to make it random you can randomize the first 2 or 3 digits of the timestamp
To avoid the problem of having to check to see if a matching code exists every time a new one is created, I just catch MySQL's duplicate record exception (error code 1062). If that error is caught, I just call the function again until the save is successful. That way, it only has to generate a new code if it collides with an existing one. Runs a lot faster -- but obviously gets a bit slower as your number of users approaches the number of possible barcodes.
function generateBarcode($user_id) {
try {
$user = User::find($user_id);
$user->barcode = mt_rand(1000000000, 9999999999);
$user->save();
} catch (Exception $e) {
$error_info = $e->errorInfo;
if($error_info[1] == 1062) {
generateBarcode($user_id);
} else {
// Only logs when an error other than duplicate happens
Log::error($e);
}
}
}
So just loop through all the users you want to assign a code to:
foreach(User::all() as $user) {
generateBarcode($user->id);
}
You could also add some logic to escape the function loop if a maximum number of attempts are made, but I've never bothered because collisions are unlikely.
I made something like this
/**
* Generate unique shipment ID
*
* @param int $length
*
* @return string
*/
function generateShipmentId($length)
{
$number = '';
do {
for ($i=$length; $i--; $i>0) {
$number .= mt_rand(0,9);
}
} while ( !empty(DB::table('shipments')->where('id', $number)->first(['id'])) );
return $number;
}