Clinic Appointment Website
Check the slot is free, then book it — and two patients get 5:30pm. The application code was never what made it correct.
- PHP
- MySQL
About this project
A clinic website with real appointment booking — services, doctor profiles, a booking flow that generates slots from each doctor's stored availability, and an admin for appointments, doctors and their weekly timings.
The booking code almost everyone writes does a SELECT to check whether 5:30pm with Dr Menon is free, and INSERTs if it comes back empty. It reads correctly and passes every manual test, because a human cannot click twice in the same millisecond. It is a race: two requests can both run the SELECT before either INSERTs, and both patients are told they have the slot. The fix is not a bigger transaction — it is a UNIQUE constraint so the database refuses the second write, with application code that turns the duplicate-key error into a clean "that slot has just been taken". The proof runs ten separate OS processes against one slot and prints the rows, and its sharpest experiment points the fixed application code at the same table with the index dropped: still ten bookings. The code was never what made it correct.
What you will learn
- Recognise check-then-act as a race, and stop trusting it in booking code
- Enforce uniqueness with a database constraint, catching the duplicate-key error properly
- Tell a duplicate slot apart from a duplicate reference — two unique keys, two meanings
- Know when a constraint cannot express the rule, and reach for a lock instead
- Generate bookable slots from stored availability rather than hardcoding a timetable
Before you start
- PHP basics, and comfort reading SQL
- PHP 8.1+ and MySQL 8 (or MariaDB 10.4+)
- Some idea of what a race condition is — the project will make it concrete
New to PHP? The free PHP course covers everything this project assumes.
Academic integrity notice
These projects are provided for learning, experimentation and reference. Understand the code, modify it for your own requirements, and acknowledge external work according to your institution’s academic policies. Priodemy does not supply work to be submitted as your own.
Features
- A concurrency proof using ten separate OS processes and ten separate MySQL connections
- The decisive experiment: the fixed code with the index dropped still double-books
- Duplicate slot vs duplicate reference — two unique keys that must not be handled the same way
- A per-phone cap that no index can express, held under concurrency with a named lock
- Slots generated from each doctor's stored availability, with a clinic holidays table
- Overlapping availability windows rejected, not silently accepted
Technology stack
Track: Web Development · Level: Major project
What is included
- The full site and admin — commented throughout
- schema.sql with doctors, availability, holidays, appointments and seed data
- proof/race_proof.php and its worker — real concurrency, not a simulation
Delivered as a 138 KB ZIP. SHA-256 published under Changelog so you can verify it.
System requirements
- Operating system
- Any
- Runtime
- PHP 8.1+ and MySQL 8 / MariaDB 10.4+. InnoDB required for the constraint and locks.
Licence
Learn from it, change it, and use it in your own coursework and personal projects. Do not resell it or pass it off as your own original work.
- Personal and educational use
- Modifying the code however you like
- Using it as the basis of your own coursework, with honest acknowledgement
- Putting your modified version in a portfolio or on your own GitHub
- Reselling or redistributing the project, modified or not
- Presenting it as your own original work
- Commercial deployment — see the Commercial licence
Support window: 6 months from download.
Frequently asked
Is the race actually demonstrated, or just described?
Demonstrated. proof/race_proof.php spawns ten separate OS processes with ten separate MySQL connections against one slot, eight trials per experiment, and prints the rows that landed. Against the naive path it shows ten patients holding the same appointment. The output in the README is what the script really printed.
Why not just wrap it in a transaction?
Because a transaction alone does not stop it — the project proves this by pointing the fully-fixed application code at the same table with the unique index dropped, and still getting ten bookings. Uniqueness is a property of the data, so the database has to own it. The application check stays as a fast path for the common case, but it is no longer what makes the system correct.
Does it handle rules a constraint cannot express?
Yes, and that contrast is part of the lesson. "At most one booking per slot" is a unique key. "At most three upcoming appointments per phone number" is a count, which no index can express — so that one uses a named lock per phone number, and the proof shows ten simultaneous requests on one number landing exactly three appointments.
Changelog
- First release
SHA-25608214055ad9d0defc2016cb51f7ffb09cde6826c62e88f86e80cfd897de70c93