overview
the final project for the back-end half of a two-year program. a complete multi-page php web application: public registration and login, a profile area, a news/citations module, and an admin panel with crud over users, news, and appointments.
the brief was vague on the what — the grade hinged on the how: clean separation of concerns, defensive coding, and the patterns a real php shop would expect to see.
architecture
two tables that split identity from credentials:
users_data— personal info (name, surname, email, phone, date of birth, address).users_login— credentials and role, joined onidUser.
registration writes both tables inside a transaction. if either insert fails, the whole operation rolls back — no orphaned rows, no half-registered accounts.
auth flow
- prepared statements throughout. no string interpolation anywhere near the database.
password_hash(..., PASSWORD_DEFAULT)for storage,password_verify()on login.session_regenerate_id(true)before writing session variables on successful login — prevents session fixation.- role-based access control via a
require_role()helper that accepts a string or an array and uses strictin_array()matching. - flash messaging via
$_SESSIONkeys that are read and immediately unset on display, so a refresh doesn't replay the message. - output escaping with
htmlspecialchars($value, ENT_QUOTES, 'UTF-8')on every variable rendered to html.
ajax
email and username availability are checked with debounced ajax from the registration form. the endpoints in ajax/ reuse the same existe_email() and existe_usuario() helpers as the server-side validation — one source of truth, no duplicated rules between the client and the server.
admin panel
three pages under admin/, each protected by require_role('admin'):
- user crud (the largest at ~14 kb)
- news crud
- appointment crud
the same patterns repeat: prepared statement, validate, transact, redirect with a flash message.
what this taught me
the exercises that mattered weren't the new features — they were the small decisions that compound. wrapping registration in a transaction. regenerating the session id before writing to it. reusing the validation helper between the form submit and the ajax endpoint. none of these are clever individually. together they make the difference between code that works and code that holds up.