you need to create observer in model
protected static function boot()
{
parent::boot();
static::saving(function ($model) {
$model->id= 'your structure here';
});
}
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
In my app I want to generate an ID for every order like 2010-5.
Which is [year][month]-[order number in that month]
And of course I want to make sure it's unique.
What do you recommend the best way to do it? generate it in store method every time I create new order? or create a DB trigger that handles that? how can I ensure the uniqueness?
Please share your ideas with me.
you need to create observer in model
protected static function boot()
{
parent::boot();
static::saving(function ($model) {
$model->id= 'your structure here';
});
}
@yazeedayyash Okay, that works for generating it, but how can I guarantee uniqueness here?
Any help?
use Carbon\Carbon;
protected static function boot()
{
parent::boot();
static::saving(function ($model) {
$prefix = Carbon::now()->format('Y-m');
$count = $model->where('created_at', 'LIKE', $prefix . '%')->count();
$model->slug = $prefix . '-' . $count++;
});
}
This is assuming you arent deleting any orders.
@automica Yeah, that's exactly what I reached so far and yea I don't physically delete any orders from DB.
The only problem I have now is, how to ensure the uniqueness of it and not getting an error for unique constrain if I get too many orders at once.
I would use UUID.
static::saving(fn($model) => $model->uuid = \Ramsey\Uuid\Uuid::uuid4());
Or older syntax.
static::saving(function ($model) {
$model->uuid = \Ramsey\Uuid\Uuid::uuid4();
});
@bugsysha Unfortunately the business people doesn't want an UUID.
@MohamedTammam you can always pull it through sha1().
static::saving(function ($model) {
$model->uuid = sha1(\Ramsey\Uuid\Uuid::uuid4());
});
@bugsysha I'd stayed away from defining the actual id, as that should either be incremental or uuid.
I would suggest OP is actually trying to define an order reference which would be more appropriate as in
YYYY-MM-{id} format
@automica Yeah, that's correct and the {id} part should reset every month.
@automica how do you mean "defining the actual id"?
@bugsysha I’d be using autoincrement integer or uuid for model id and what I suggested previously tor order reference
@automica what would be the benefit of such approach? Auto-incrementing ID can be used to keep things more readable. UUID can be used on a presentation layer. One doesn't exclude the other. But still can't figure out why would you want to have such a format for the orders.
@bugsysha Year, Month and current order number that month is much easier to parse for a human than a UUID. its obvious that
2021-10-001 and 2021-10-002 are orders from the same month.
@bugsysha It's a business requirement.
@automica I understand the readability side, but I was hoping there was something else I was missing. For me that is only important for invoicing systems. For all other use cases, I would go with something "random". If you check Amazon you will see that they don't use such an approach. But thanks for clarifying.
@MohamedTammam business requirements are sometimes stupid and you should point those situations out. Like I said, only invoicing systems to some extent make sense for such a format. Where you need people to be able to roughly guess the invoice number. But even that is debatable. If you are going with the mentioned format then shouldn't you somehow represent a user also in the invoice number? That way you can remember some important customers and find their orders even easier.
Generate it using the last inserted id, read about this in mysql manual.
Like last id is 578, now build what you need from that, i.e., 2010-578
Just suggestion.
Edit:
See https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id
Search and read paragraph beginning with:
The ID that was generated is maintained in the server on a per-connection basis. This means that the value returned ...
@jlrdw Can't do that, because I need the last digit to reset to 1 every new month.
@MohamedTammam I agree with @bugsysha above, there are times when you have to explain these things and go with plan b. It will be hard to get unique numbers like you asked. You could monthly have a table of numbers, use one and delete from table, but that will not always prevent duplicates.
Just saying when you have multi-users at same time, this has been a problem for many web programmers. Note also that even using random sometimes repeats.
The table per month with numbers could work but you would have to implement locking on that table.
@jlrdw I will of course try to explain that to them, but for now I'm testing all approach on another column not the ID column. so at least I'm preventing errors with relationship if I had duplicates.
can't think of a good way to do this without introducing the possibility of a race condition where two orders are being created at the same time and get the same number.
Ignoring race condition for now, you need to find the highest number so far in this month (database query) then increase it and then construct the new reference.
The problem is that between your read of the database to find the highest record, and your saving of the order reference, another user in another thread could also read the database and get the same answer.
The only solution I know to avoid this is to lock the table between the query and the insert.
@Snapey thank you. One more point to why it needs to be scoped with the user-id in the order number.
A safer option...
Don't store the order reference at all. Insert all your orders using autoincrement id and then calculate the orderRef on the fly when you need to display it.
You can get all the order id's for the month of the order, and then find the position of the order in the collection.
eg, in your order model;
public function getOrderRefAttribute()
{
$year = $this->created_at->format('Y');
$month = $this->created_at->format('m');
$monthOrders = Order::whereYear('created_at',$year)
->whereMonth('created_at',$month)
->orderBy('id',ASC)
->pluck('id');
$position = $monthOrders->search($this->id);
$orderRef = $year . $month . '-' . $position+1;
return $orderRef;
}
Warnings:
i think you can use sleep in queue, for example
for ($i = 0; $i < 10; $i++) {
dispatch(function () use ($i) {
sleep(10); // make sure sleep is top of other code
Log::error($i);
});
}
result, look the seconds part
[2021-10-08 09:37:23] local.ERROR: 0
[2021-10-08 09:37:33] local.ERROR: 1
[2021-10-08 09:37:43] local.ERROR: 2
[2021-10-08 09:37:53] local.ERROR: 3
[2021-10-08 09:38:03] local.ERROR: 4
[2021-10-08 09:38:13] local.ERROR: 5
[2021-10-08 09:38:23] local.ERROR: 6
[2021-10-08 09:38:33] local.ERROR: 7
[2021-10-08 09:38:43] local.ERROR: 8
[2021-10-08 09:38:53] local.ERROR: 9
@newbie360 what is the pricing model is based on execution time?
maybe something like this, will this work ? imagine create 1000 invoice records at the same time, but the create action is proccess one by one
// use queue on create Invoice
dispatch(function () {
sleep(5); // make sure sleep is top of other code
$lastInvoiceNumber = Invoice::whereYear('created_at', now()->year)
->whereMonth('created_at', now()->month)
->orderByDesc('id')
->value('invoice_number'); // get first record value
$newInvoiceNumber = is_null($lastInvoiceNumber)
? now()->format('Y-m-000001') // starting a new month format: 2021-11-000001
: ++$lastInvoiceNumber;
Invoice::create(['inovice_number' => $newInvoiceNumber]);
});
@newbie360 what on earth are you on about?
sleep ?
have you heard of a counter?
This is really worrying considering you give others advice
Counter? can you explain
the thing you learn in the first hour of php. $counter++
you mean this ?
++$lastInvoiceNumber;
$a = '2021-10-000011';
dd(++$a); // '2021-10-000012
yes i'm newbie for coding, i think remove sleep(5) is ok
and the code block isn't inside a loop, just imagine 100 peoples adding inovice at the same time, all goes to jobs table then proccess one by one
@newbie360 know that sleep(), usleep() and other similar purpose features are not scalable solutions. That should "never" be part of your code. Also, as pointed out by @snapey, that might have concurrency issues.
@bugsysha oh ok i will avoid use sleep(), Thank you
The best way I got so far.
I added unique constrain to the order number then I add UUID as a default value, that way I will make sure that the order will be created without errors with a unique value.
CreateOrdersTable
Schema::create('orders', function (Blueprint $table) {
$table->id();
// ....
$table->string('order_number')->unique()->default(DB::raw('UUID()'));
$table->timestamps();
});
Then I created an observer and in created method I try to update the order number 3 times, if for some reason it didn't pass after 3 times I will keep the UUID value.
CreateOrdersTable.php
public function created(Order $order) {
$saved = false;
$counter = 0;
do {
if($counter >= 3)
break;
$prefix = Carbon::now()->format('ym') . '-';
$orderNumber = Order::whereMonth('created_at', date('m'))->whereYear('created_at', date('Y'))->count();
$order->order_number = $prefix . $orderNumber;
$counter += 1;
try {
$order->save();
$saved = true;
} catch (QueryException $e) {
echo 'Exception \n';
$saved = false;
}
} while (!$saved);
}
Now it works so far. I'm thinking of making a job that keep checking everyday or so if we have UUID value instead of the desired format then change it.
Please share your opinion with that approach.
@MohamedTammam catching up afterwards will be a problem if you value the sequence of the numbers since the catch up order number will be at the end, and possibly also in a different month.
@Snapey I have created at and the normal ID which is auto_increment that I can validate using them.
That what got in my mind so far. Because that number only business-related stuff so I can only use them if I want to show the order ID.
Please or to participate in this conversation.