Уеб Разработка

Simple Booking 2.3: плъгин за резервации в WordPress, който не може да презапише час

Пренаписах плъгина си за резервации от нулата и изнесох защитата от двойни записвания от PHP в ограничение на базата данни. Ето защо старата версия губеше състезанието, какво се промени в модела на данните и какво вече е безплатно.

wordpressплъгинрезервацииmysqlphpsimple-booking

Версия 1 беше писана за един салон, под краен срок, и работеше — точно докато двама души не отвориха формата по едно и също време. Тогава направи онова, което всяка система за резервации трябва да прави невъзможно: прие и двамата.

Simple Booking 2.3 е пренаписването. Бизнес правилата бяха пренесени; имплементацията — не. Тази статия е за четирите решения, които се промениха, защото те решават дали един плъгин за резервации ще издържи в натоварен петък.

Защо версия 1 губеше състезанието

Старият код правеше това, което прави почти всеки плъгин за резервации:

if ( slot_is_free( $staff_id, $start ) ) {
    insert_booking( $staff_id, $start, $customer );
}

Прочетете го внимателно и ще видите, че грешката е между двата реда. Две заявки могат да изпълнят slot_is_free(), и двете да получат true, и двете да продължат към записа. Прозорецът е няколко милисекунди — и точно затова оцелява при тестване и се появява в реална работа, в онзи следобед, когато промоция изпрати тридесет души на една и съща страница.

Този прозорец не се затваря от PHP. Може да се свие — с транзакции, SELECT ... FOR UPDATE и заключвания — но честното решение е въпросът изобщо да не се задава в PHP.

Двойното записване е ограничение, а не проверка

Във версия 2.x всяка резервация носи slot_key: дайджест от служителя и началния момент, под уникален индекс.

Две едновременни заявки за един и същ час изчисляват един и същ дайджест. Първият запис минава. Вторият се проваля не защото приложението е забелязало, а защото базата данни го отказва. Няма прозорец, защото няма разстояние между решаването и записването — това е една операция.

Отказана резервация занулява ключа, а MySQL допуска неограничен брой NULL стойности в уникален индекс, така че часът отново става свободен без допълнително счетоводство.

Тестът, който има значение, изстрелва пет едновременни заявки към един час по реален HTTP:

Concurrency: 5 simultaneous requests for one slot
  PASS  exactly one of five concurrent requests won (1×201, 4×409)

Това се изпълнява срещу жив сайт, не срещу макет.

Един двигател за наличност, а не три

Версия 1 отговаряше на въпроса „свободен ли е този час?“ на три места: в календара за клиента, в календара в администрацията и в проверката преди записа. Започнаха еднакви и се разминаха, както винаги става с дублирана логика. Всяко двойно записване, което тя някога произведе, беше място, където две от трите не бяха съгласни — обикновено за услуга с буфер или за ден с еднократна промяна в графика.

Във версия 2.x има един AvailabilityEngine и трите места минават през него. Звучи като подредба. Не е: това е истинската поправка, а ограничението отгоре е предпазната мрежа под нея.

Времената се пазят в UTC

Всеки момент е UTC колона, а часовата зона на произход се записва до нея.

Изкушаващата алтернатива е да се пази „стенно“ време — чете се приятно в базата и съвпада с това, което вижда клиентът. Тя обаче прави всяко сравнение мълчаливо локално, което значи, че се чупи два пъти годишно на границата на лятното часово време и още веднъж в деня, в който преместите сайта на сървър в друг регион. Съхранението в UTC струва едно преобразуване по краищата и премахва цял клас доклади за грешки, които идват месеци по-късно и не могат да бъдат възпроизведени.

Резервацията пази два интервала, а не един

Това е финото място и започна живота си като икономия.

Една резервация има:

  • start_utc / end_utc — часът така, както го разбира клиентът. Показва се, изпраща се по имейл, експортира се.
  • blocked_start_utc / blocked_end_utc — заетостта в календара, разширена с времето за подготовка и почистване на услугата. Това е, което се сблъсква.

Версия 1 имаше една двойка, която носеше и двата смисъла, и това причиняваше две грешки едновременно. На клиентите се показваха двайсетте минути за почистване като част от собствения им час. А броенето на местата при групови услуги тихо спираше да работи за всяка услуга с буфер, защото броеното вече не беше същото като резервираното.

Разделянето им премахна и двата проблема и направи кода по-четим — обикновено така се разбира, че сливането е било грешка още в началото.

409 не е 400

Още едно разграничение, на което разчита клиентската част. И двете се провалят на една и съща проверка за наличност:

  • 409 Conflict — часът е бил реален и някой друг току-що го е взел. Обновявате часовете и клиентът избира друг. Поправимо е.
  • 400 Bad Request — исканото време никога не е било предлагано. Проблемът е в заявката, не в момента.

Ако ги слеете в един код, показвате „този час вече не е свободен, опитайте отново“ на човек, чиято заявка няма да успее никога, колкото и пъти да опита.

Какво е безплатно и какво не

Безплатният плъгин е на tanchev.net/simple-booking и е цялата система за резервации:

  • Неограничени услуги, служители и резервации — без никакви лимити
  • Цени за всяка услуга, платими на място
  • Групови резервации за курсове и работилници
  • График за много служители, със седмично работно време, почивки и празници за всеки
  • Имейли за потвърждение, отказ и напомняне, с текст, който можете да редактирате
  • Календар в администрацията с дневен, седмичен и месечен изглед
  • Импорт с едно кликване от версия 1.x

Има една платена добавка, Simple Booking Pro, и тя добавя две неща: известия по SMS и WhatsApp през Twilio и приемане на депозити онлайн през вашия WooCommerce checkout.

Това разделяне не е маркетингово решение, а ограничение, което си струва да знаете, ако някога планирате публикуване в WordPress.org: директорията отхвърля плъгини със заключена или обфускирана функционалност. Платените функции не могат да стоят в безплатния плъгин зад проверка на лиценз. Затова безплатният плъгин излага точки за разширение — feature gate, регистър на каналите, регистър на платежните шлюзове — а добавката ги запълва. Практическата последица е, че добавянето на SMS не изисква никаква промяна в безплатния плъгин.

Депозитите са интересният случай, защото показват какво всъщност струва това правило. Приемането на пари онлайн беше безплатно. Превръщането му в платена функция не стана с добавяне на проверка за лиценз пред кода — точно тази проверка е заключената функционалност, която директорията отхвърля, и щеше да падне на ревю. Стана с изнасяне на WooCommerce шлюза от безплатния плъгин в добавката, така че безплатният плъгин наистина вече не съдържа платежен шлюз. Портата пред него сега не отказва нищо, което се разпространява.

При плъгин, който искате в директорията, имате един от два избора: или взимате пари за функция, като не я разпространявате, или я разпространявате и не взимате пари за нея. Трети вариант, който минава на ревю, няма. Всичко, което остана безплатно — групови резервации, много служители, редактируеми текстове на известията — остана, защото кодът му е още в безплатния плъгин, а катинар, нарисуван върху работещ код, е точно това, което води до отхвърляне.

Една последица заслужава да се каже направо, защото иначе се превръща в тикет за поддръжка: сайт без добавката запазва настройките си за депозит и спира да ги прилага. Резервациите се потвърждават веднага, вместо да чакат плащане, което нищо на сайта не може да приеме. Настройката си стои непокътната за деня, в който дойде лиценз.

Откъде да го вземете

Изисква WordPress 6.4+ и PHP 8.0+. WooCommerce е нужен само ако искате да приемате депозити онлайн, и тогава ви трябва и добавката: тя създава поръчката, а WooCommerce обработва картата.

Свързани статии