Záznamník zámkov
SQLite kompiluje lockForUpdate() na prázdny reťazec, takže test nerozozná uzamknuté čítanie od neuzamknutého — a skutočné FOR UPDATE je na SQLite syntaktická chyba. Dva pozorovateľné varianty zapisujú každý zámok aj hĺbku transakcie, v ktorej prebehol, do LockRecorder. Zachytia súbehy, ktoré by na SQLite zostali neviditeľné: upozornenie, ktoré sa nikdy neotvorilo, úroveň eskalácie upozornenú dvakrát, predaný tovar nad stav skladu.
Variant A — model sa dá rozšíriť
Pridajte trait RecordsLocks do testovacej podtriedy. Vráti LockRecordingBuilder, ktorý zaznamená každé volanie lock(), lockForUpdate() a sharedLock() a potom ho odovzdá ďalej, takže príkaz prebehne nezmenený:
use Illuminate\Support\Facades\DB;
use RoundlyConsulting\Testing\Fixtures\Concerns\RecordsLocks;
use RoundlyConsulting\Testing\Fixtures\LockRecorder;
// Variant A — the model is subclassable:
final class RecordingCoupon extends Coupon { use RecordsLocks; }
it('locks the coupon inside the transaction', function (): void {
LockRecorder::flush();
DB::transaction(fn () => RecordingCoupon::query()->lockForUpdate()->get());
expect(LockRecorder::recorded())->toHaveCount(1)
->and(LockRecorder::recorded()[0]['marker'])->toBe('lock-for-update')
->and(LockRecorder::recorded()[0]['transactionDepth'])->toBe(1);
});Variant B — zámok je v kóde, ktorý nemožno vymeniť
Nainštalujte LockRecordingGrammar na SQLite pripojenie. Zámok skompiluje na koncový SQL komentár, ktorý SQLite bez problémov vykoná, a listenForMarkers() cez DB::listen() zaznamená každý označený dopyt:
use Illuminate\Support\Facades\DB;
use RoundlyConsulting\Testing\Fixtures\LockRecorder;
use RoundlyConsulting\Testing\Fixtures\LockRecordingGrammar;
// Variant B — the model is not subclassable, or the lock is buried in an action:
it('locks the shop row', function (): void {
$connection = DB::connection();
$connection->setQueryGrammar(new LockRecordingGrammar($connection));
LockRecorder::flush();
LockRecorder::listenForMarkers();
DB::transaction(fn () => Shop::query()->lockForUpdate()->get());
expect(LockRecorder::recorded()[0]['sql'])->toContain('/* lock-for-update */')
->and(LockRecorder::recorded()[0]['transactionDepth'])->toBe(1);
});Prečo záleží na hĺbke transakcie
Zámok, ktorý skončí v savepointe uvoľnenom ešte pred zápisom do účtovnej knihy, je skutočná trieda chýb — práve kvôli nej neprešiel zamýšľaný pomocník na zamykanie. Hĺbka ju zviditeľní; vnorená transakcia zaznamená hĺbku 2:
DB::transaction(function (): void {
DB::transaction(function (): void {
RecordingCoupon::query()->lockForUpdate()->get();
});
});
// The nested transaction is a savepoint — depth 2, not the outer transaction.
expect(LockRecorder::recorded()[0]['transactionDepth'])->toBe(2);API LockRecorder
| Metóda | Účel |
|---|---|
LockRecorder::flush() | Vyprázdni záznam — volajte v setUp() a pred spustením súbehu. |
LockRecorder::recorded() | Zámky od posledného vyprázdnenia v poradí: zoznam položiek marker, sql a transactionDepth. |
LockRecorder::listenForMarkers() | Zapojenie variantu B — DB::listen(), ktorý zaznamená každý označený dopyt. Zavolajte raz, po nainštalovaní gramatiky. |
LockRecorder::record($marker, $transactionDepth, $sql = '') | Ručné zaznamenanie jedného zámku. |
Značky sú lock-for-update, lock-shared a lock-custom (textová klauzula zámku). Variant A zaznamená zámok pri volaní metódy buildera s prázdnym sql; variant B zaznamená celý vykonaný príkaz.
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.