トランザクション内でペシミスティック ロックを使用する必要があります。
Product
エンティティに、count
残っているアイテムの数を含むフィールドがあるとします。ユーザーがアイテムを購入した後、そのフィールドを減らします。
この場合、悲観的な書き込みロックが必要です。基本的に、悲観的ロックを取得しようとする他のプロセスによって行が読み取られたり更新されたりするのを防ぎます。これらのプロセスは、行をロックしたトランザクションがコミットまたはロールバックによって終了するか、タイムアウトになるまでロックされたままになります。
したがって、トランザクションを開始し、ロックを取得し、十分なアイテムが残っているかどうかを確認し、それらを注文に追加し、アイテムの数を減らして、トランザクションをコミットします。
$em->beginTransaction();
try {
$em->lock($product, LockMode::PESSIMISTIC_WRITE);
if ($product->getCount() < $numberOfItemsBeingPurchased) {
throw new NotEnoughItemsLeftInStock;
}
$order->addItem($product, $numberOfItemsBeingPurchased);
$product->decreaseCount($numberOfItemsBeingPurchased);
$em->commit();
} catch (Exception $e) {
$em->rollback();
throw $e;
}
2 人のユーザーが同時に最後のアイテムを購入することは例外的な状況であるため、ここで例外をスローすることをお勧めします。もちろん、このコードを実行する前に、何らかの項目数チェック (検証制約など) を使用する必要があります。そのため、ユーザーがそのチェックを通過したが、別のユーザーがチェックの後、現在のユーザーが実際に購入する前に最後のアイテムを購入した場合、それは例外的な状況です。
また、単一のHTTP リクエスト中にトランザクションを開始および終了する必要があることにも注意してください。つまり、1 つの HTTP リクエストで行をロックせず、ユーザーが購入を完了するのを待ち、その後にのみロックを解除します。現実世界のカートのように、ユーザーがしばらくの間カートにアイテムを保持できるようにしたい場合は、在庫に残っているアイテムの数を減らしてしばらくの間ユーザーのために製品を予約するなど、他の手段を使用してください。その数のアイテムを元に戻すことで、タイムアウト後に解放します。