Softwareentwicklung

Neugestaltung der TrailRun-Website

MooiWeer

Auftraggeber
MooiWeer
Kategorie
Softwareentwicklung
Jahr
2025
Technologien
PHPJavascriptMySQLPDFLib
TrailRun Terschelling

Eine vollständige Registrierungs- und Ticketplattform für eine Trail Running-Veranstaltung auf einer niederländischen Waddensee-Insel , die von Grund auf mit einem benutzerdefinierten PHP-Framework, Live-Zahlungsverarbeitung, PDF-Ticket-Generation und automatisierter E-Mail-Lieferung gebaut wurde.

3Rennstrecken
~ 22kLinien von PHP
PDFlibFahrkartenmaschine
IDEALErste Zahlung
2025Jahr der Neugestaltung

Was ist das? Das Projekt?

TrailRun Terschelling ist die Rennregistrierungs-Website für ein jährliches Wettlaufveranstaltung auf der Insel Terschelling in den Niederlanden. Teilnehmer können sich online für die 10 km, 15 km oder 25 km Entfernung anmelden, über mehrere niederländische Zahlungsmethoden bezahlen und sofort ein PDF-Rennticket mit einem einzigartigen Zugangsschlüssel erhalten.

Das Projekt umfasst die gesamte Teilnehmerreise von Ende zu Ende: ein informatives Frontend mit Rennrouten, die auf interaktiven Karten visualisiert werden, ein mehrstufiges AJAX-Registrierungsformular mit Server-Bestätigung, eine Live-Payment-Gateway-Integration, eine PDF-Generations-Pipeline und ein automatisiertes Transaktions-E-Mail-System alle durch ein heimisches leichtes MVC-Framework miteinander verbunden.

Im Jahr 2025 wurde das gesamte Frontend neu gestaltet (neue CSS-Architektur, benutzerdefinierte Eigenschaften, Flexbox-Layout, Mobile-First-Breakpoints) und das zugrunde liegende PHP wurde auf PHP 8 Kompatibilität geprüft, während die Codebase auf dem Produktionsserver bereitgestellt werden konnte, der auf PHP 5.6 beschränkt ist.

PHP 5.6 / 8MySQLPDFlibPHPMailerMailgun SMTPEMS-Zahlungs-GatewayIDEALjQueryDas Flugblatt.jsCSS benutzerdefinierte Eigenschaften

Gepflegte MVC Rahmenbedingungen

Anstatt Laravel oder Symfony zu erreichen, basiert dieses Projekt auf einem leichten benutzerdefinierten Framework, das speziell für diese Art von Veranstaltungsregistrierungs-Anwendungsfall entwickelt wurde. Das Framework ist klein, schnell und vom Entwickler vollständig verstanden.

NSWebpage Basisseite Shell

Erstellt das HTML-Dokument-Skelett, injiziert SEO-Meta-Tags, lädt CSS/JS-Assets und delegiert Inhalte in Unterklassen durch eine Abstraktion drawPage() Methode.

NSAactiveWebpage AJAX Dispatcher

Erweitert NSWebpage mit Sitzungsmanagement und einer ereignisorientierten AJAX-Schicht. NSEvent Gegenstände und durch eine handleEvent() Der Dispatcher.

NSPpersistentObject Leichtgewicht ORM

Basisklasse für alle Datenbankmodelle. Implementiert ein schmutziges Flaggenmuster Eigenschaften werden nur in die Datenbank geschrieben, wenn sie geändert werden. loadObject(), store()und delete() ohne Schema-Reflexionsüberschuss.

NSD-Datenbank Singleton-Verbindung

Ein Singleton-Wrapper herum mysqli Das System stellt eine einzige Datenbankverbindung pro Anfrage sicher. escape() Hilfe und eine einheitliche Abfragenoberfläche, die von allen Modellen verwendet wird.

Seitenklassen

Jede Route führt zu einer Betonseitenklasse, die sich erstreckt TrailrunWebpage (sich selbst erstreckt) NSActiveWebpageDas Singleton der Seite wird in $_SESSION, erhalten Sie den Formularzustand über AJAX-Anrufe ohne versteckte Felder oder wiederholte Datenbank-Rundfahrten.

// All routing goes through .htaccess → index.php
// index.php routes to the correct page class

IndexPage      → Home / hero slider / news
RegisterPage   → Multi-step registration form
RoutesPage     → Leaflet map + GPX route overlay
InformatiePage → Static race info content

Die Reservierung System

Die Registrierung erfolgt über ein mehrstufiges AJAX-Formular, das den Status bei jeder Änderung mit dem Server synchronisiert, so dass nichts verloren geht, wenn der Browser geschlossen wird oder die Sitzung vor der Zahlung abläuft.

Zahlungsfluss

01Auswahl der Entfernung10 / 15 / 25 km
02TeilnehmerdetailsAJAX-Adresse-Aktualisierung
03Methode auswähleniDEAL / MC / PayPal
04EMS-GatewayUmleiten
05RückrufPOST → inschreiben.php
06PDF + E-MailAutomatisch erzeugt

Datenmodell

Die Buchung wird in drei verwandte Einheiten aufgeteilt, wobei die Anliegen eindeutig getrennt sind:

ModellRolleSchlüsselfelder
Die SchlagzeileHauptbuchungsregister, Teilnehmerinformationen, ZahlungszustandBuchingsnummer, Zugangsschlüssel, Staat, E-Mail, Geburtsdatum
Elemente für die BewegungEinzelne Linienpunkte (eine pro Abstand/Slot ausgewählt)Event_id, Preis, Rabatt, Menge
BezahlungUnveränderliche ZahlungstransaktionsprotokolleZahlungsgegenstand, Betrag, Währung, Marke, Status, IP-Adresse
EreignisAus der Datenbank geladene RennkonfigurationName der Veranstaltung, Preis für Erwachsene, Slots, Mindestalter, Aktivität

Staatsmaschine

Jede Buchung führt durch eine strenge Reihe von Staaten, verhindert Doppelzahlungen und macht Auditfragen trivial:

STATENOPAYMENT  (1)  → Created, awaiting payment
STATEPAID       (2)  → Payment confirmed, PDF + email sent
STATECHANGE     (3)  → Booking modified post-payment
STATECANCELED   (4)  → Cancelled / refunded

Spielautomatenverwaltung

Die EventFactory die Datenbank bei Seitebelastung abfragt und die Anzahl der verfügbaren Slots pro Entfernung berechnet, indem die bestätigten Buchungen vom konfigurierten Maximum abgezogen werden. Wenn eine Entfernung ausverkauft wird, wird sie automatisch ohne manuelle Eingriffe aus der Auswahloberfläche entfernt.

Der Zugriffsschlüssel, der auf jeder BoekingElement wird mit Toolbox::MakeRandomPassword(8) eine kryptographisch zufällige 8-Zeichen-String, die auf dem PDF-Ticket gedruckt und vom Rennpersonal zur Überprüfung der Teilnehmer an der Startlinie verwendet wird.

Zahlungsgateway Integration

Die Zahlungen werden über das EMS-E-Commerce-Gateway verarbeitet, das die drei wichtigsten Methoden für eine niederländische Sportveranstaltung unterstützt: iDEAL (direkte Banküberweisung), Mastercard und PayPal.

  1. 1
    Ordnungsbildung Wenn der Teilnehmer auf "Bezahlen" klickt, sendet der Kunde ein Zahlungsereignis (payment_ideal, payment_mastercardoder payment_paypal) über AJAX. Der Server ruft EMSPayment::sendPaymentRequest(), die eine HMAC-Signatur über die Auftragsparameter berechnet und eine Umleitung URL zurückgibt.
  2. 2
    Gateway-Überleitung Der Browser wird auf die von EMS gehostete Zahlungsseite weitergeleitet. Alle sensiblen Kartenverarbeitung erfolgt auf der Domäne des Gateways die Anwendung berührt nie Kartennummern.
  3. 3
    Asynchrone Rückruf Nach der Zulassung stellt das EMS das Ergebnis an inschrijven.php. Der Handler bestätigt die HMAC-Signatur, um zu bestätigen, dass die Antwort echt ist, und liest dann chargetotal, txndatetime, und der Genehmigungscode.
  4. 4
    Abschluss Auf approval_code == 'Y' und status == 'APPROVED', die Buchung ist gezahlt gekennzeichnet, die vollständige Transaktionsunterlagen werden an boeking_payment, und die PDF + E-Mail-Pipeline wird ausgelöst. PAYMENTFAILURE und der Benutzer sieht einen freundlichen Fehler.
Die Codebase enthält auch eine vergangene Ogone-Zahlungsintegration das Vorgänger-Gateway. Sie ist nicht mehr aktiv, sondern wurde als Referenz in der Codebase aufbewahrt, da einige historische Buchungen über sie gemacht wurden und das Antwortformat immer noch in Kommentaren dokumentiert wird.

PDF-Ticket-Generation mit PDFlib

Jede bestätigte Registrierung erzeugt eine personalisierte, druckbereite PDF-Rennenkarte. PDFlib, eine kommerzielle PDF-Schaffungsbibliothek, die wegen ihrer präzisen Layout-Steuerung und zuverlässiger Schriftwiedergabe ausgewählt wurde.

Die Fahrkartenfabrik (framework/factories/PDFTicket.php) komponiert das Dokument programmatisch es gibt keine Vorlagedatei, keine HTML-zu-PDF-Konvertierung und kein Zwischen-Render-Schritt. Jedes Element wird mit exakten Punktkoordinaten auf einer A4-Leinwand (595 × 842 pt) platziert.

Was steht auf dem Ticket?

Schlagzeilen

Ein vollwertiges Markenbild (pdf_ticketkop.jpg) oben auf der Seite wird die Rassenidentität und das Jahr festgelegt.

Fahrkarte

Ein entfernungsspezifisches Kartenbild (pdf_10KM.jpg, pdf_15KM.jpg, pdf_25KM.jpg) wird auf der Grundlage der gebuchten Veranstaltung zur Laufzeit geladen und jedem Ticket eine einzigartige visuelle Signatur verleiht.

Teilnehmerblock

Name, Adresse, Postcode, Stadt und ausgewählte Entfernung werden in Helvetica-Bold eingestuft. Das Renndatum (28-02-2027) und die entfernungsspezifische Startzeit (13:00 / 12:30 / 11:45) werden in großem Typ gedruckt.

Zugangsschlüssel + Buchungsnummer

Der 8-Zeichen-Zugangsschlüssel wird für die Start-Line-Verifizierung hervorragend gedruckt.

Wie das Bauwerk funktioniert

// Simplified excerpt from PDFTicket::BuildTicket()

$pdf = new PDFlib();
$pdf->set_option("license=m900102-...");
$pdf->begin_document("pdf_gen_tickets/{$nr}.pdf", "");
$pdf->begin_page_ext(595, 842, "");      // A4

// Place branded header image
$img = $pdf->load_image("jpeg", "pdf_ticketkop.jpg", "");
$pdf->fit_image($img, 0, 700, "boxsize={595 130}");

// Distance-specific route map
$routeImg = $pdf->load_image("jpeg", "pdf_{$km}KM.jpg", "");
$pdf->fit_image($routeImg, 300, 480, "boxsize={280 200}");

// Participant name in Helvetica-Bold 14pt
$font = $pdf->load_font("Helvetica-Bold", "winansi", "");
$pdf->setfont($font, 14);
$pdf->show_xy($naam, 40, 650);

// Amount in large 24pt type
$pdf->setfont($font, 24);
$pdf->show_xy("EUR {$amount}", 40, 560);

$pdf->end_page_ext("");
$pdf->end_document("");

Das fertige PDF wird auf pdf_gen_tickets/{BookingNumber}.pdf auf der Festplatte, wo sie sofort zur Verfügung steht, um die Bestätigungs-E-Mail beizufügen.

PDFlib wurde über HTML-zu-PDF-Konverter wie wkhtmltopdf gewählt, weil es bietet Deterministische Pixel-perfekte Layout Für ein Ticketing-System, in dem ein fehlerhafter Element das Rennpersonal verwirren könnte, lohnt sich dieser Kompromiss.

Transaktions-E-Mail Pipeline

Die E-Mails zur Bestätigung werden in dem Moment verschickt, in dem eine Zahlung überprüft wird. PostOffice Fabrik wickelt PHPMailer und routert die ausgehende Mail durch Mailguns SMTP-Relee für eine zuverlässige Lieferbarkeit.

  1. 1
    Vorlagebelastung Die HTML-E-Mail-Vorlage wird von framework/mail_templates/reserveringsbevestiging.html eine eigenständige HTML-Datei, die von Nichtentwicklern bearbeitet werden kann, ohne PHP zu berühren.
  2. 2
    Substitution von Token Platzinhaber-Token wie z. B. [FIRSTNAME], [RESERVATIONNUMBER]und [BOOKINGDATA] Die Reservierungsaufschlusstabelle wird als Inline-HTML dargestellt.
  3. 3
    PDF-Anhänge Das neu erstellte Ticket PDF (pdf_gen_tickets/{BookingNumber}.pdfDie Teilnehmer erhalten ihr Ticket innerhalb von Sekunden nach der Zahlungsbestätigung.
  4. 4
    SMTP-Lieferung PHPMailer sendet an die Adresse des Teilnehmers über smtp.eu.mailgun.org:25Eine schweigende BCC wird an die Veranstalter der Veranstaltung für ihre eigenen Aufzeichnungen kopiert. Schreiben Sie die Bestätigung TrailRun Terschelling 2027 [TRT-00123].
// PostOffice::sendConformation() — core logic

$body = file_get_contents('framework/mail_templates/reserveringsbevestiging.html');
$body = str_replace('[FIRSTNAME]',          $boeking->Voorletters(),   $body);
$body = str_replace('[RESERVATIONNUMBER]',   $boeking->Boekingsnummer(), $body);
$body = str_replace('[BOOKINGDATA]',         $itemTable,               $body);

$mail->AddAttachment("pdf_gen_tickets/{$nr}.pdf");
$mail->AddBCC('info@mooi-weer.nl');
$mail->Send();

Front End & 2025 Neugestaltung

Die Neugestaltung von 2025 ersetzte ein datiertes Layout durch ein modernes, mobil-erst-Design-System, während die zugrunde liegende PHP möglichst unberührt gehalten wurde. Die Designsprache spiegelt absichtlich den robusten Außencharakter der Veranstaltung wider.

CSS benutzerdefinierte Eigenschaften

Ein einzelner :root Die Art und Weise, wie die Markenfarben für ein zukünftiges Jahr ausgetauscht werden, erfordert eine Bearbeitung, nicht eine Finding-and-Replacement über 2000 Zeilen.

Flexbox zum ersten Mal für Mobilgeräte

Die Navigation kollabiert zu einem Hamburgermenü auf dem Handy; Rennkarten fließen von einem mehrspaltenden Gitter zu einer einzigen Spalte ohne Layout JavaScript zurück.

Kein Grenzradius absichtlich

Jede Karte, jeder Knopf und jeder Eingabe haben quadratische Ecken, um das industrielle Gefühl von Rennfahrzeugen, Streckenmarkierungen und Geländemappen nachzudrücken.

Fahrbuch.js-Route-Karten

Jede Rennstrecke verfügt über eine spezielle GPX-Route, die auf einer OpenStreetMap-Fliesen-Schicht über das Leaflet erstellt wird.#99c038) und animierte Markierungen zeigen wichtige Wegpunkte an.

AJAX-Registrierungsformular

Die RegisterPage JavaScript-Klasse sendet eine addressupdate Bei jeder Feldänderung wird ein Ereignis an den Server übertragen. Serverseitige Validierungsfehler werden als JSON zurückgegeben und inline angezeigt keine vollständige Seite neu geladen, keine verlorenen Daten.

Jssor-Held-Slider

Ein Full-Viewport-Bild-Slider auf der Startseite durch Rennfotografie. Touch-Swipe-Unterstützung funktioniert aus der Box auf Mobilgeräten ohne zusätzliche Konfiguration.

Bauen Sie Artefakte

CSS ist in css/design.css (1752 Zeilen) und minifiziert design.min.css JavaScript für das Registrierungsformular wird von js/inschrijven.js zu inschrijven.min.jsDie ... .htaccess verwendet gzip-Kompression für CSS-, JS- und Schriftdateien und setzt aggressive Cache-Header für statische Assets.

PHP Kompatibilität Herausforderung

Die Produktions-Hosting-Umgebung ist auf PHP 5.6 beschränkt, aber die Codebase muss auch eine PHP 8 Syntaxprüfung bestehen und sauber unter PHP 8 auf der Entwicklungsmaschine laufen. Dies ist die schwierigste Einschränkung für das Projekt.

Die Arbeiten 2025 beinhalteten die Prüfung jeder PHP-Datei auf Features, die sich über die Versionlücke hinweg unterschiedlich verhalten:

  1. 1
    Mysqli_* über mysql* Alle Vermächtnisse mysql_query(), mysql_fetch_array(), und damit verbundene Anrufe wurden durch ihre mysqli_* Äquivalente, die sowohl in PHP 5.6 als auch in PHP 8 erhältlich sind.
  2. 2
    Schnittstelle des Iterators PHP 8 spaltet das alte Iterator Schnittstelle in Iterator und IteratorAggregate Mit einer strengeren Durchsetzung des Return-Types wurden benutzerdefinierte Sammelklassen aktualisiert, um die richtige Schnittstelle zu implementieren, und Return-Typ-Erklärungen wurden hinzugefügt, wenn PHP 5.6 sie harmlos ignoriert.
  3. 3
    String-Index-Syntax PHP 8 verweigert die Indekzierung von Kurbelsträngen ($str{0}) Alle Ereignisse wurden in Bracket-Syntax umgewandelt ($str[0]), die für beide Versionen gültig ist.
  4. 4
    Keine Tippinhalte für Parameter PHP 5.6 unterstützt weder Scallar-Typ-Hints noch Return-Types in Funktionssignaturen.

Schreiben PHP, die auf beiden läuft 5.6 und 8 Die Strategie bestand darin, nur die Teilmenge von PHP zu verwenden, die beide Versionen teilen, und jede Funktion zu vermeiden, die nach 5.6 eingeführt wurde, für die PHP 8 später das Verhalten geändert hat.

Vollständige Technik Stack

Jede aus praktischen Gründen gewählte Komponente kein Rahmen für sich selbst, keine Abhängigkeit, die ihr Gewicht nicht zieht.

SchichtTechnologieWarum?
ServersprachePHP 5.6 / 8Aufnahmegeschränkung; keine Wahl möglich
DatenbankMySQL (mysqli)Verfügbar auf Host; ausreichend für das Datenvolumen
RahmenbedingungenBenutzerdefinierte (NSWebseite / MVC)Keine Laravel / Symfony auf PHP 5.6 ohne Lösungen
PDF-GenerationPDFlib (kommerziell)Pixel-perfekte Layout; deterministische Ausgabe; Schriftzeichen Einbettung
E-Mail-LieferungPHPMailer + Mailgun SMTPZuverlässige Lieferfähigkeit; BCC-Audit-Pfad an die Organisatoren
ZahlungsgatewayEMS E-Commerce (HMAC)Unterstützt iDEAL, Mastercard, PayPal in einer einzigen Integration
KartenLeaflet.js + OpenStreetMapOpen-Source; keine API-Schlüssel erforderlich; GPX-Overlay trivial
DOM-ManipulationjQuery 1.11.1Stabil; Arbeiten auf dem alten Browser-Range Event-Teilnehmer verwenden
HeldenschieberJssorTouch-Swipe aus der Box; Null-Server-Abhängigkeit
CSS-Architekturbenutzerdefinierte CSS-EigenschaftenKeine Vorverarbeitung benötigt; benutzerdefinierte Eigenschaften unterstützt nativ
Nachweis von GerätenMobile_Detect.phpHinweis auf Server-Breakpoint zusätzlich zu Medienanfragen

Unterricht & Mitnehmen

Was dieses Projekt über die Feature-Liste hinaus zeigt.

Einschränkungen führen zu einem guten Entwurf

Die PHP 5.6 Einschränkung zwang ein benutzerdefiniertes Framework, das klein, vollständig verständlich und leicht zu überprüfen ist. Keine versteckte Magie, keine Versioninkompatibilitäten, die im Lieferantenkode vergraben sind.

Zu Hause angebaute ORM ≠ schlechte ORM

Das schmutzige Flagge-Muster in NSPersistentObject Es ist ein legitimes Muster, das in vielen großen Rahmenwerken verwendet wird. Manchmal ist es besser, ein kleines Rad neu zu erfinden, als ein großes zu ziehen.

AJAX-Zustand-Synchronisierung schlägt versteckte Felder

Speichern Sie die Seite Singleton in $_SESSION Die Synchronisierung bei jeder Feldänderung bedeutet, dass die Teilnehmerdaten sicher sind, auch wenn der Browser im mittleren Format abstürzt.

PDF-Generation ist keine Ware

Für ein Ticketing-System, in dem die Layoutgenauigkeit für den betrieblichen Gebrauch wichtig ist, war eine richtige PDF-Bibliothek die Lizenzgebühr wert.

Gestaltungsbeschränkungen als Markenentscheidungen

Die Regel, keine Grenzstraßen zu haben, begann als eine Zeitersparung und wurde zu einer bewussten Markenwahl.

Doppelversion PHP ist lösbar

Das Schreiben für PHP 5.6 und PHP 8 gleichzeitig ist selten, aber handlungsfähig. Der Schlüssel ist, genau zu wissen, welche Funktionen zu vermeiden sind und die Beschränkung für zukünftige Mitarbeiter klar zu dokumentieren.

Projektbilder

Weitere Arbeiten

Ähnliche Projekte

Starten Sie ein Projekt mit uns

Sie haben ein Projekt im Sinn? Sprechen wir darüber, wie wir Sie unterstützen können.