Learn/flight_vs_laravel
Flight vs Laravel
Was ist Laravel?
Laravel ist ein vollständiges Framework mit allen Klingeln und Pfiffen und einem beeindruckenden, auf Entwickler fokussierten Ökosystem, aber zu Lasten von Leistung und Komplexität. Das Ziel von Laravel ist es, dass der Entwickler das höchste Maß an Produktivität erreicht und gängige Aufgaben einfach macht. Laravel ist eine großartige Wahl für Entwickler, die eine vollständige, unternehmensorientierte Webanwendung aufbauen möchten. Das geht mit einigen Kompromissen einher, speziell in Bezug auf Leistung und Komplexität. Der Einstieg in Laravel kann einfach sein, aber die Meisterschaft im Framework zu erlangen, kann einige Zeit in Anspruch nehmen.
Es gibt auch so viele Laravel-Module, dass Entwickler oft das Gefühl haben, der einzige Weg, Probleme zu lösen, sei die Nutzung dieser Module, obwohl man tatsächlich einfach eine andere Bibliothek verwenden oder eigenen Code schreiben könnte.
Vorteile im Vergleich zu Flight
- Laravel hat ein riesiges Ökosystem aus Entwicklern und Modulen, das für die Lösung gängiger Probleme genutzt werden kann.
- Laravel hat ein vollständiges ORM, das für die Interaktion mit Ihrer Datenbank verwendet werden kann.
- Laravel hat eine verrückte Menge an Dokumentation und Tutorials, die für das Lernen des Frameworks genutzt werden können. Das kann gut sein, um in die Details einzutauchen, oder schlecht, weil es einfach so viel zu durchgehen gibt.
- Laravel hat ein integriertes Authentifizierungssystem, das zur Sicherung Ihrer Anwendung verwendet werden kann.
- Laravel hat Podcasts, Konferenzen, Meetings, Videos und andere Ressourcen, die für das Lernen des Frameworks genutzt werden können.
- Laravel ist auf einen erfahrenen Entwickler ausgerichtet, der eine vollständige, unternehmensorientierte Webanwendung aufbauen möchte.
Nachteile im Vergleich zu Flight
- Laravel hat viel mehr unter der Haube als Flight. Das geht mit einem dramatischen Kosten in Bezug auf Leistung einher. Schauen Sie sich die TechEmpower-Benchmarks für weitere Informationen an.
- Flight ist auf einen Entwickler ausgerichtet, der eine leichte, schnelle und einfach zu bedienende Webanwendung aufbauen möchte.
- Flight ist auf Einfachheit und Benutzerfreundlichkeit ausgerichtet.
- Eine der Kernfunktionen von Flight ist, dass es sein Bestes tut, um Abwärtskompatibilität zu wahren. Laravel verursacht viel Frustration zwischen großen Versionen.
- Flight ist für Entwickler gedacht, die zum ersten Mal in die Welt der Frameworks eintauchen.
- Flight hat keine Abhängigkeiten, wohingegen Laravel eine abscheuliche Menge an Abhängigkeiten hat
- Flight kann auch unternehmensorientierte Anwendungen umsetzen, hat aber nicht so viel Boilerplate-Code wie Laravel. Es erfordert auch mehr Disziplin vom Entwickler, um Dinge organisiert und gut strukturiert zu halten.
- Flight gibt dem Entwickler mehr Kontrolle über die Anwendung, wohingegen Laravel massenhaft Magie im Hintergrund hat, die frustrierend sein kann.
Learn/migrating_to_v3
Migration zu v3
Die Abwärtskompatibilität wurde größtenteils beibehalten, aber es gibt einige Änderungen, die Sie beachten sollten, wenn Sie von v2 zu v3 migrieren. Es gibt einige Änderungen, die zu sehr mit Designmustern kollidiert sind, sodass Anpassungen vorgenommen werden mussten.
Verhalten des Output Buffering
v3.5.0
Output buffering ist der Prozess, bei dem die Ausgabe, die von einem PHP-Skript generiert wird, in einem Puffer (intern in PHP) gespeichert wird, bevor sie an den Client gesendet wird. Dies ermöglicht es Ihnen, die Ausgabe zu modifizieren, bevor sie an den Client gesendet wird.
In einer MVC-Anwendung ist der Controller der "Manager" und er verwaltet, was die View tut. Ausgaben, die außerhalb des Controllers generiert werden (oder im Fall von Flight manchmal eine anonyme Funktion), brechen das MVC-Muster. Diese Änderung dient dazu, mehr im Einklang mit dem MVC-Muster zu sein und das Framework vorhersehbarer und einfacher zu bedienen zu machen.
In v2 wurde das Output Buffering so gehandhabt, dass es seinen eigenen Output-Puffer nicht konsistent schloss, was Unit-Tests und Streaming schwieriger machte. Für die Mehrheit der Nutzer könnte diese Änderung Sie tatsächlich nicht beeinflussen. Wenn Sie jedoch Inhalte außerhalb von Callables und Controllern ausgeben (z. B. in einem Hook), stoßen Sie wahrscheinlich auf Probleme. Das Ausgeben von Inhalten in Hooks und vor der tatsächlichen Ausführung des Frameworks hat in der Vergangenheit möglicherweise funktioniert, wird aber künftig nicht mehr funktionieren.
Wo Sie Probleme haben könnten
// index.php
require 'vendor/autoload.php';
// nur ein Beispiel
define('START_TIME', microtime(true));
function hello() {
echo 'Hello World';
}
Flight::map('hello', 'hello');
Flight::after('hello', function(){
// das wird tatsächlich in Ordnung sein
echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});
Flight::before('start', function(){
// Dinge wie das werden einen Fehler verursachen
echo '<html><head><title>My Page</title></head><body>';
});
Flight::route('/', function(){
// das ist tatsächlich in Ordnung
echo 'Hello World';
// Das sollte auch in Ordnung sein
Flight::hello();
});
Flight::after('start', function(){
// das wird einen Fehler verursachen
echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</div></body></html>';
});
Aktivieren des v2-Rendering-Verhaltens
Können Sie Ihren alten Code so lassen, wie er ist, ohne eine Umstellung durchzuführen, um ihn mit v3 kompatibel zu machen? Ja, das können Sie! Sie können das v2-Rendering-Verhalten aktivieren, indem Sie die Konfigurationsoption flight.v2.output_buffering auf true setzen. Dies ermöglicht es Ihnen, das alte Rendering-Verhalten weiterhin zu verwenden, aber es wird empfohlen, es künftig zu beheben. In v4 des Frameworks wird dies entfernt werden.
// index.php
require 'vendor/autoload.php';
Flight::set('flight.v2.output_buffering', true);
Flight::before('start', function(){
// Jetzt wird das in Ordnung sein
echo '<html><head><title>My Page</title></head><body>';
});
// mehr Code
Änderungen am Dispatcher
v3.7.0
Wenn Sie statische Methoden für Dispatcher direkt aufgerufen haben, wie z. B. Dispatcher::invokeMethod(), Dispatcher::execute() usw., müssen Sie Ihren Code aktualisieren, um diese Methoden nicht mehr direkt aufzurufen. Dispatcher wurde zu einem objektorientierteren Ansatz umgewandelt, damit Dependency Injection Container einfacher verwendet werden können. Wenn Sie eine Methode ähnlich wie der Dispatcher aufrufen müssen, können Sie manuell etwas wie $result = $class->$method(...$params); oder call_user_func_array() verwenden.
Änderungen an halt() stop() redirect() und error()
v3.10.0
Das Standardverhalten vor 3.10.0 war, sowohl die Header als auch den Response-Body zu löschen. Dies wurde geändert, sodass nur noch der Response-Body gelöscht wird. Wenn Sie auch die Header löschen müssen, können Sie Flight::response()->clear() verwenden.
Learn/configuration
Konfiguration
Überblick
Flight bietet eine einfache Möglichkeit, verschiedene Aspekte des Frameworks an die Bedürfnisse Ihrer Anwendung anzupassen. Einige sind standardmäßig eingestellt, aber Sie können sie nach Bedarf überschreiben. Sie können auch eigene Variablen festlegen, die in Ihrer gesamten Anwendung verwendet werden.
Klare, mehrschichtige Konfiguration (Datei-Standardwerte + Umgebungsgeheimnisse) hilft auch KI-Codierungstools: Agenten lernen einen Ort für Literale und einen Ort für Geheimnisse, anstatt $_ENV-Lesezugriffe in Controllern zu erfinden.
Verständnis
Sie können bestimmte Verhaltensweisen von Flight anpassen, indem Sie Konfigurationswerte über die set-Methode festlegen.
Flight::set('flight.log_errors', true);
In einer strukturierten Anwendung (einschließlich des Skeletts) laden Sie typischerweise Projekteinstellungen aus app/config/config.php und wenden dann relevante Schlüssel auf die Engine an (z. B. flight.base_url, flight.views.path). Sie können auch ein kleines Konfigurationsobjekt in Controller injizieren, anstatt überall Globale zu lesen – freundlicher für Tests und für Agenten, die AGENTS.md folgen.
Grundlegende Verwendung
Flight-Konfigurationsoptionen
Die folgende Liste enthält alle verfügbaren Konfigurationseinstellungen:
- flight.base_url
?string- Überschreibt die Basis-URL der Anfrage, wenn Flight in einem Unterverzeichnis läuft. (Standard: null) - flight.case_sensitive
bool- Groß-/Kleinschreibung bei URL-Abgleich beachten. (Standard: false) - flight.handle_errors
bool- Flight erlaubt, alle Fehler intern zu behandeln. (Standard: true)- Wenn Sie möchten, dass Flight Fehler anstelle des Standard-PHP-Verhaltens behandelt, muss dies true sein.
- Wenn Sie Tracy installiert haben, setzen Sie dies auf false, damit Tracy Fehler behandeln kann.
- Wenn Sie das APM Plugin installiert haben, setzen Sie dies auf true, damit das APM die Fehler protokollieren kann.
- flight.log_errors
bool- Fehler in die Fehlerprotokolldatei des Webservers schreiben. (Standard: false)- Wenn Sie Tracy installiert haben, protokolliert Tracy Fehler basierend auf den Tracy-Konfigurationen, nicht auf dieser Konfiguration.
- flight.debug
bool- Detaillierte Fehlerinformationen (Ausnahmemeldung, Code und Stack-Trace) im Browser ausgeben, wenn ein Fehler auftritt. (Standard: false)- Aktivieren Sie dies niemals in der Produktion – es gibt interne Anwendungsdetails preis. Verwenden Sie es nur für lokale Entwicklung oder Staging.
- Wenn
false, wird stattdessen eine generische500 Internal Server Error-Antwort angezeigt. Kombinieren Sie dies mitflight.log_errors, um Fehler serverseitig zu erfassen.
- flight.allow_method_override
bool- Ermöglicht das Überschreiben der HTTP-Methode über denX-HTTP-Method-Override-Anfrageheader oder ein_method-Feld im POST-Body. (Standard: true)- Das Setzen auf
falsewird empfohlen für Anwendungen, die kein HTML-Formular-basiertes Methoden-Spoofing benötigen, da es Clients verhindert,DELETE- oderPUT-Anfragen über ein Standard-POST-Formular zu fälschen. - Siehe Sicherheit für weitere Details.
- Das Setzen auf
- flight.views.path
string- Verzeichnis mit View-Template-Dateien. (Standard: ./views) - flight.views.extension
string- Dateiendung für View-Templates. (Standard:.php; das offizielle Skelett setzt dies auf.twig, wenn Twig verwendet wird) - flight.content_length
bool- DenContent-Length-Header setzen. (Standard: true)- Wenn Sie Tracy verwenden, müssen Sie dies auf false setzen, damit Tracy korrekt rendern kann.
- flight.v2.output_buffering
bool- Legacy-Ausgabepufferung verwenden. Siehe Migration zu v3. (Standard: false)
Loader-Konfiguration
Zusätzlich gibt es eine weitere Konfigurationseinstellung für den Loader. Diese ermöglicht es Ihnen, Klassen mit _ im Klassennamen automatisch zu laden.
// Aktiviert das Laden von Klassen mit Unterstrichen
// Standardmäßig true
Loader::$v2ClassLoading = false;
Denken Sie daran, dass Autoloading auch von der Groß-/Kleinschreibung der Ordner abhängt, die zu Ihren Namespaces passen muss – insbesondere beim Skelett-Layout mit App\ + app/Controller/.
Projektkonfiguration und .env (Skelett-Muster)
Der Flight-Kern erfordert keine .env-Dateien. Viele Anwendungen verwenden nur ein PHP-Konfigurationsarray. Das offizielle Skelett schichtet die Konfiguration, sodass Geheimnisse nicht in Git gelangen, während Runway weiterhin literale Konfiguration sicher umschreiben kann:
.env/ echte Umgebung — Geheimnisse und Deployment-Überschreibungen (gitignoriert).app/config/config.php— Literale PHP-Array-Standardwerte (kopiert ausconfig_sample.php). Bevorzugen Sie keine$_ENV[...]-Ausdrücke in dieser Datei: Tools wierunway config:setkönnten sie als statische Werte umschreiben und Geheimnisse in die Datei backen.- Zusammenführen beim Bootstrap — Umgebung gewinnt für zugeordnete Schlüssel; Anwendungscode liest ein Konfigurationsobjekt oder
$app->get(), nicht$_ENVin Controllern.
Beispielstruktur von config_sample.php / config.php (vereinfacht):
<?php
// Nur Literale – Geheimnisse gehören für den Skelett-Workflow in .env
return [
'app' => [
'env' => 'development',
'debug' => true,
'base_url' => '/',
'timezone' => 'UTC',
],
'database' => [
'driver' => 'sqlite', // oder mysql, oder '' zum Deaktivieren
'host' => 'localhost',
'dbname' => '',
'user' => '',
'password' => '',
'file_path' => __DIR__ . '/../../database.sqlite',
],
// ...
];
# .env.example → .env (Skelett)
APP_ENV=development
APP_DEBUG=true
FLIGHT_BASE_URL=/
DB_DRIVER=sqlite
# DB_PASSWORD=...
Diese Trennung ist bewusst für KI-freundliche Projekte: Anweisungen können sagen: „Standardwerte in config.php, Geheimnisse in .env, Config / Engine injizieren – niemals Umgebungszugriff in einem Controller erfinden.“ Bestehende Anwendungen können .env vollständig ignorieren und eine einzige Konfigurationsdatei behalten.
Variablen
Flight ermöglicht es Ihnen, Variablen zu speichern, sodass sie überall in Ihrer Anwendung verwendet werden können.
// Speichern Sie Ihre Variable
Flight::set('id', 123);
// Anderswo in Ihrer Anwendung
$id = Flight::get('id');
Um zu prüfen, ob eine Variable gesetzt wurde, können Sie Folgendes tun:
if (Flight::has('id')) {
// Etwas tun
}
Sie können eine Variable löschen, indem Sie Folgendes tun:
// Löscht die id-Variable
Flight::clear('id');
// Löscht alle Variablen
Flight::clear();
Hinweis: Nur weil Sie eine Variable setzen können, bedeutet das nicht, dass Sie es tun sollten. Verwenden Sie diese Funktion sparsam. Der Grund dafür ist, dass alles, was hier gespeichert wird, zu einer globalen Variable wird. Globale Variablen sind schlecht, weil sie von überall in Ihrer Anwendung geändert werden können, was das Auffinden von Fehlern erschwert. Außerdem kann dies Dinge wie Unit-Tests verkomplizieren. Bevorzugen Sie Konstruktor-Injektion (wie im Skelett- und Dice-Setup) für Dienste und Konfiguration, die Controller benötigen.
Fehler und Ausnahmen
Alle Fehler und Ausnahmen werden von Flight abgefangen und an die error-Methode übergeben, wenn flight.handle_errors auf true gesetzt ist.
Das Standardverhalten besteht darin, eine generische HTTP 500 Internal Server Error-Antwort mit einigen Fehlerinformationen zu senden.
Sie können dieses Verhalten für Ihre eigenen Bedürfnisse überschreiben:
Flight::map('error', function (Throwable $error) {
// Fehler behandeln
echo $error->getTraceAsString();
});
Standardmäßig werden Fehler nicht an den Webserver protokolliert. Sie können dies aktivieren, indem Sie die Konfiguration ändern:
Flight::set('flight.log_errors', true);
404 Nicht gefunden
Wenn eine URL nicht gefunden werden kann, ruft Flight die notFound-Methode auf. Das Standardverhalten besteht darin, eine HTTP 404 Not Found-Antwort mit einer einfachen Meldung zu senden.
Sie können dieses Verhalten für Ihre eigenen Bedürfnisse überschreiben:
Flight::map('notFound', function () {
// Nicht gefunden behandeln
});
Siehe auch
- Installation - Skelett-Konfiguration,
.envund Bootstrap-Layout. - Autoloading - Namespaces und Groß-/Kleinschreibung von Ordnern.
- Flight erweitern - So erweitern und passen Sie die Kernfunktionalität von Flight an.
- Unit-Tests - So schreiben Sie Unit-Tests für Ihre Flight-Anwendung.
- KI & Entwicklererfahrung -
AGENTS.mdund konsistente Projektanweisungen. - Tracy - Ein Plugin für erweiterte Fehlerbehandlung und Debugging.
- Tracy-Erweiterungen - Erweiterungen zur Integration von Tracy mit Flight.
- APM - Ein Plugin für Anwendungsleistungsüberwachung und Fehlerverfolgung.
- Sicherheit - Härtungsflags und Geheimnisbehandlung.
Fehlerbehebung
- Wenn Sie Probleme haben, alle Werte Ihrer Konfiguration herauszufinden, können Sie
var_dump(Flight::get());ausführen. - Wenn Runway oder Deployment-Tools
config.phpumgeschrieben haben, bestätigen Sie, dass keine Geheimnisse committet wurden – bewahren Sie sie in.envoder in der echten Umgebung auf, wenn Sie das Skelett-Muster verwenden.
Änderungsprotokoll
- Doku – Skelettartige Konfiguration /
.env-Schichtung und Twig-View-Erweiterungsstandard für neue Projekte dokumentiert. - v3.18.1 - Konfigurationsoptionen
flight.debugundflight.allow_method_overridehinzugefügt. - v3.5.0 - Konfiguration für
flight.v2.output_bufferinghinzugefügt, um das Legacy-Ausgabepufferungsverhalten zu unterstützen. - v2.0 - Kernkonfigurationen hinzugefügt.
Learn/ai
KI & Entwicklererfahrung mit Flight
Übersicht
Flight ist dafür konzipiert, mit KI-Programmierwerkzeugen zu arbeiten – nicht gegen sie. Eine kleine, vorhersehbare API, ein klares App-Layout im offiziellen Skeleton und projektspezifische Anweisungsdateien bedeuten, dass Assistenten wie GitHub Copilot, Cursor, Windsurf, Claude Code und Gemini dieselben Muster befolgen können, die du von Hand schreiben würdest.
Mit eingebauten Runway-Befehlen zum Verbinden mit LLM-Anbietern und zum Generieren von Projektanweisungen hilft Flight dir und deinem Team, konsistente, relevante Hilfe zu erhalten, ohne denselben Kontext in jeden Chat einzufügen.
Verständnis
KI-Programmierassistenten sind am hilfreichsten, wenn sie den Kontext, die Konventionen und die Ziele deines Projekts verstehen. Die KI-Helfer von Flight ermöglichen dir:
- Verbinde dein Projekt mit beliebten LLM-Anbietern (OpenAI, Grok, Claude usw.)
- Generiere und aktualisiere projektspezifische Anweisungen, damit alle dieselbe Anleitung erhalten
- Halte handgeschriebenen und KI-generierten Code in einem einheitlichen Layout (insbesondere mit dem Skeleton)
Diese Funktionen sind in der Flight-Core-CLI enthalten (über Runway) und im offiziellen flightphp/skeleton Starter vorkonfiguriert.
Was das Skeleton für KI mitbringt
Der offizielle Starter behandelt AGENTS.md als die Quelle der Wahrheit für KI-Tools:
| Datei | Rolle |
|---|---|
AGENTS.md (Projektwurzel) |
Globale Regeln, Boot-Ablauf, Namensräume, DI, „Was man nicht tun sollte“ |
Bereichsbezogene AGENTS.md unter app/, migrations/, tests/ usw. |
Kompakte, ordnerspezifische Tipps, wenn du in diesem Verzeichnisbaum arbeitest |
SECURITY.md |
Geheimnisse, Header, XSS/SQL, Meldung – Sicherheit bleibt bewusst getrennt |
Es gibt keine separate House-Style-Datei für Copilot / Cursor / Gemini / Windsurf im Skeleton. Weise deinen Assistenten auf die AGENTS.md im Wurzelverzeichnis an (und lass ihn Links zu bereichsbezogenen Dateien folgen). Menschen können diese Dateien komplett ignorieren und die README verwenden; das Layout ist in beiden Fällen dasselbe.
Dokumente lehren APIs; das Skeleton lehrt Layout. Kurze
Flight::-Beispiele in diesen Dokumenten sind hervorragend zum Lernen. In einer Skeleton-App bevorzugeApp\…-Klassen, Konstruktorinjektion und$this->appgegenüber der statischen Fassade in Controllern. Siehe Installation und Autoloading.
Grundlegende Verwendung
Einrichten der LLM-Anmeldeinformationen
Der Befehl ai:init führt dich durch das Verbinden deines Projekts mit einem LLM-Anbieter.
php runway ai:init
Du wirst aufgefordert:
- Wähle deinen Anbieter (OpenAI, Grok, Claude usw.)
- Gib deinen API-Schlüssel ein
- Lege die Basis-URL und den Modellnamen fest
Dies erstellt die Anmeldeinformationen, die für spätere LLM-Anfragen verwendet werden (zum Beispiel zum Generieren von Anweisungen).
Beispiel:
Welcome to AI Init!
Which LLM API do you want to use? [1] openai, [2] grok, [3] claude: 1
Enter the base URL for the LLM API [https://api.openai.com]:
Enter your API key for openai: sk-...
Enter the model name you want to use (e.g. gpt-4, claude-3-opus, etc) [gpt-4o]:
Credentials saved to .runway-creds.json
Generieren projektspezifischer KI-Anweisungen
Der Befehl ai:generate-instructions erstellt oder aktualisiert Anweisungen für KI-Programmierassistenten, zugeschnitten auf dein Projekt.
php runway ai:generate-instructions
Du beantwortest ein paar Fragen (Beschreibung, Datenbank, Templating, Sicherheit, Teamgröße usw.). Flight verwendet deinen LLM-Anbieter, um Anweisungen zu generieren, und schreibt sie hauptsächlich in:
AGENTS.mdim Projektwurzelverzeichnis (werkzeugunabhängig; das, was das offizielle Skeleton und die meisten modernen Agenten erwarten)
Je nach CLI-Version und Optionen kann der Befehl auch toolspezifische Kopien für ältere Workflows schreiben (zum Beispiel Copilot-, Cursor-, Windsurf- oder Gemini-Regeldateien). Behandle bei neuen Projekten aus dem Skeleton AGENTS.md (plus alle bereichsbezogenen AGENTS.md-Dateien, die du unter app/ behältst) als einzige Quelle der Wahrheit – pflege nicht fünf abweichende Anweisungsdateien von Hand.
Beispiel:
Please describe what your project is for? My awesome API
What database are you planning on using? MySQL
What HTML templating engine will you plan on using (if any)? twig
Is security an important element of this project? (y/n) y
...
AI instructions updated successfully.
Jetzt können KI-Tools Code vorschlagen, der zu deinem tatsächlichen Stack und Layout passt – nicht zu einem generischen PHP-Tutorial.
Fortgeschrittene Verwendung
- Passe Anmeldeinformationen oder Ausgabepfade mit Befehlsoptionen an (siehe
--helpbei jedem Befehl). - Die Helfer funktionieren mit jedem LLM-Anbieter, der eine OpenAI-kompatible API spricht.
- Führe
ai:generate-instructionserneut aus, wenn sich das Projekt weiterentwickelt, damit Agenten synchron bleiben. - Im Skeleton solltest du die Sicherheitsrichtlinie in
SECURITY.mdund das Code-Layout inAGENTS.mdaufbewahren, damit kein Dokument zu einem Sammelsurium wird. - Bevorzuge docs.flightphp.com und den Flight-MCP-Server, wenn Agenten API-Details benötigen; überprüfe erfundene Methoden anhand von
vendor/flightphp/core.
Siehe auch
- Flight Skeleton – Offizieller Starter mit
AGENTS.md, Twig, SimplePdo und Dice, verdrahtet für eine KI-freundliche Struktur - Installation – Empfohlenes
create-project-Layout - Autoloading – Ordner-Großschreibung entspricht Namensräumen (
App\Controller↔app/Controller/) - Runway CLI – CLI, die
ai:*- und Scaffolding-Befehle antreibt - Sicherheit – Sichere Standardeinstellungen, die Agenten (und Menschen) nicht schwächen sollten
Fehlerbehebung
- Wenn du „Missing .runway-creds.json“ siehst, führe zuerst
php runway ai:initaus. - Stelle sicher, dass dein API-Schlüssel gültig ist und Zugriff auf das ausgewählte Modell hat.
- Wenn Anweisungen nicht aktualisiert werden, überprüfe die Dateiberechtigungen in deinem Projektverzeichnis.
- Wenn ein Agent Flight-APIs oder das falsche Ordnerlayout erfindet, weise ihn auf die
AGENTS.mdim Wurzelverzeichnis und diese Dokumentationsseite hin; das Skeleton-Layout hat für Code unterapp/Vorrang.
Änderungsprotokoll
- v3.18.4 –
ai:generate-instructionsschreibt Projektanweisungen inAGENTS.mdim Projektwurzelverzeichnis. - v3.16.0 –
ai:init- undai:generate-instructions-CLI-Befehle für die KI-Integration hinzugefügt.
Learn/unit_testing_and_solid_principles
Dieser Artikel wurde ursprünglich 2015 auf Airpair veröffentlicht. Alle Credits gehen an Airpair und Brian Fenton, der den Artikel ursprünglich geschrieben hat, obwohl die Website nicht mehr verfügbar ist und der Artikel nur in der Wayback Machine existiert. Dieser Artikel wurde der Seite zu Lern- und Bildungszwecken für die PHP-Community hinzugefügt.
1 Einrichtung und Konfiguration
1.1 Aktuell bleiben
Lassen Sie uns das von Anfang an klären – eine deprimierend kleine Anzahl von PHP-Installationen in der Praxis ist aktuell oder wird aktuell gehalten. Ob das auf Einschränkungen bei Shared-Hosting, Standardeinstellungen, die niemand ändert, oder auf fehlender Zeit/Budget für Upgradetests zurückzuführen ist, die bescheidenen PHP-Binaries werden oft zurückgelassen. Eine klare Best Practice, die mehr Betonung verdient, ist daher, immer eine aktuelle Version von PHP zu verwenden (5.6.x zum Zeitpunkt dieses Artikels). Darüber hinaus ist es wichtig, regelmäßige Upgrades sowohl von PHP selbst als auch von Erweiterungen oder Vendor-Bibliotheken durchzuführen. Upgrades bringen neue Sprachfunktionen, verbesserte Geschwindigkeit, geringeren Speicherverbrauch und Sicherheitsupdates. Je häufiger Sie upgraden, desto weniger schmerzhaft wird der Prozess.
1.2 Sinnvolle Standardeinstellungen
PHP macht einen anständigen Job, gute Standardeinstellungen mit seinen Dateien php.ini.development und php.ini.production vorzunehmen, aber wir können es besser machen. Zum einen legen sie keine Datums-/Zeitzone für uns fest. Das ergibt Sinn aus Sicht der Distribution, aber ohne eine wird PHP einen E_WARNING-Fehler auslösen, wannever wir eine datums-/zeitbezogene Funktion aufrufen. Hier sind einige empfohlene Einstellungen:
- date.timezone – wählen Sie aus der Liste der unterstützten Zeitzonen
- session.savepath – wenn wir Dateien für Sessions und nicht einen anderen Save-Handler verwenden, legen Sie das auf etwas außerhalb von /tmp fest. Wenn das als /tmp belassen wird, kann das in einer Shared-Hosting-Umgebung riskant sein, da /tmp_ in der Regel weit offene Berechtigungen hat. Sogar mit dem Sticky-Bit gesetzt, kann jeder mit Zugriff auf den Inhalt dieses Verzeichnisses alle aktiven Session-IDs erfahren.
- session.cookie_secure – das ist ein No-Brainer, schalten Sie das ein, wenn Sie Ihren PHP-Code über HTTPS servieren.
- session.cookie_httponly – stellen Sie das ein, um PHP-Session-Cookies vor dem Zugriff über JavaScript zu schützen
- Mehr... verwenden Sie ein Tool wie iniscan, um Ihre Konfiguration auf häufige Schwachstellen zu testen
1.3 Erweiterungen
Es ist auch eine gute Idee, Erweiterungen zu deaktivieren (oder zumindest nicht zu aktivieren), die Sie nicht verwenden, wie Datenbank-Treiber. Um zu sehen, was aktiviert ist, führen Sie den phpinfo()-Befehl aus oder gehen Sie zur Kommandozeile und führen Sie das aus.
$ php -i
Die Informationen sind die gleichen, aber phpinfo() hat HTML-Formatierung hinzugefügt. Die CLI-Version ist einfacher zu pipen und mit grep zu filtern, um spezifische Informationen zu finden. Zum Beispiel:
$ php -i | grep error_log
Ein Haken bei dieser Methode: Es ist möglich, dass unterschiedliche PHP-Einstellungen für die webseitige Version und die CLI-Version gelten.
2 Composer verwenden
Das könnte überraschen, aber eine der besten Praktiken für modernes PHP-Schreiben ist, weniger davon zu schreiben. Obwohl es wahr ist, dass man, um gut zu programmieren, programmieren muss, gibt es eine große Anzahl von Problemen, die im PHP-Bereich bereits gelöst wurden, wie Routing, grundlegende Input-Validierungsbibliotheken, Einheitenumwandlung, Datenbank-Abstraktionsschichten usw. Schauen Sie einfach auf Packagist und stöbern Sie herum. Sie werden wahrscheinlich feststellen, dass erhebliche Teile des Problems, das Sie lösen möchten, bereits geschrieben und getestet wurden.
Obwohl es verlockend ist, den gesamten Code selbst zu schreiben (und es ist nichts Falsches daran, Ihren eigenen Framework oder Ihre eigene Bibliothek als Lernerfahrung zu schreiben), sollten Sie gegen diese Gefühle von „Nicht von mir erfunden“ ankämpfen und sich Zeit und Kopfschmerzen sparen. Folgen Sie stattdessen der Doktrin von PIE – Proudly Invented Elsewhere. Und wenn Sie sich entscheiden, Ihr eigenes Etwas zu schreiben, veröffentlichen Sie es nicht, es sei denn, es tut etwas signifikant anderes oder Besseres als bestehende Angebote.
Composer ist ein Paketmanager für PHP, ähnlich wie pip in Python, gem in Ruby und npm in Node. Es ermöglicht Ihnen, eine JSON-Datei zu definieren, die die Abhängigkeiten Ihres Codes auflistet, und es wird versuchen, diese Anforderungen zu erledigen, indem es die notwendigen Code-Bundles herunterlädt und installiert.
2.1 Composer installieren
Wir gehen davon aus, dass dies ein lokales Projekt ist, also installieren wir eine Instanz von Composer nur für das aktuelle Projekt. Navigieren Sie zu Ihrem Projektverzeichnis und führen Sie das aus:
$ curl -sS https://getcomposer.org/installer | php
Beachten Sie, dass das Pipen eines Downloads direkt in einen Skript-Interpreter (sh, ruby, php usw.) ein Sicherheitsrisiko darstellt, also lesen Sie den Installationscode und stellen Sie sicher, dass Sie damit einverstanden sind, bevor Sie einen solchen Befehl ausführen.
Aus Gründen der Bequemlichkeit (wenn Sie lieber composer install tippen als php composer.phar install), können Sie diesen Befehl verwenden, um eine einzelne Kopie von Composer global zu installieren:
$ mv composer.phar /usr/local/bin/composer
$ chmod +x composer
Sie müssen diese möglicherweise mit sudo ausführen, je nach Ihren Dateiberechtigungen.
2.2 Composer verwenden
Composer hat zwei Hauptkategorien von Abhängigkeiten, die es verwalten kann: „require“ und „require-dev“. Abhängigkeiten, die als „require“ aufgelistet sind, werden überall installiert, aber „require-dev“-Abhängigkeiten werden nur installiert, wenn sie explizit angefordert werden. Normalerweise handelt es sich dabei um Tools für die aktive Entwicklung, wie PHP_CodeSniffer. Die Zeile unten zeigt ein Beispiel, wie man Guzzle installiert, eine beliebte HTTP-Bibliothek.
$ php composer.phar require guzzle/guzzle
Um ein Tool nur für Entwicklungszwecke zu installieren, fügen Sie die --dev-Flag hinzu:
$ php composer.phar require --dev 'sebastian/phpcpd'
Das installiert PHP Copy-Paste Detector, ein weiteres Code-Qualitäts-Tool als Entwicklungs-abhängigkeit.
2.3 Install vs. Update
Wenn wir composer install das erste Mal ausführen, installiert es alle Bibliotheken und ihre Abhängigkeiten, basierend auf der composer.json-Datei. Wenn das erledigt ist, erstellt Composer eine Lock-Datei, passend benannt composer.lock. Diese Datei enthält eine Liste der Abhängigkeiten, die Composer für uns gefunden hat, und ihre genauen Versionen mit Hashes. Jedes Mal, wenn wir composer install in Zukunft ausführen, schaut es in die Lock-Datei und installiert genau diese Versionen.
composer update ist ein bisschen anders. Es ignoriert die composer.lock-Datei (falls vorhanden) und versucht, die neuesten Versionen jeder Abhängigkeit zu finden, die immer noch den Einschränkungen in composer.json entsprechen. Es schreibt dann eine neue composer.lock-Datei, wenn es fertig ist.
2.4 Autoloading
Sowohl composer install als auch composer update generieren einen Autoloader für uns, der PHP sagt, wo es alle notwendigen Dateien für die Bibliotheken findet, die wir gerade installiert haben. Um ihn zu verwenden, fügen Sie einfach diese Zeile hinzu (normalerweise zu einer Bootstrap-Datei, die bei jeder Anfrage ausgeführt wird):
require 'vendor/autoload.php';
3 Gute Designprinzipien befolgen
3.1 SOLID
SOLID ist ein Akronym, das uns an fünf Schlüsselprinzipien im guten objektorientierten Software-Design erinnert.
3.1.1 S - Single Responsibility Principle
Das besagt, dass Klassen nur eine Verantwortung haben sollten, oder anders ausgedrückt, sie sollten nur einen Grund zum Ändern haben. Das passt gut zur Unix-Philosophie von vielen kleinen Tools, die eine Sache gut machen. Klassen, die nur eine Sache tun, sind viel einfacher zu testen und zu debuggen und überraschen Sie weniger. Sie wollen nicht, dass ein Methodenaufruf zu einer Validator-Klasse DB-Datensätze aktualisiert. Hier ist ein Beispiel für eine Verletzung des SRP, wie man es in einer Anwendung basierend auf dem ActiveRecord-Pattern häufig sieht.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
public function save() {}
}
Das ist ein ziemlich grundlegendes Entity-Modell. Eines dieser Dinge gehört hier nicht hin. Die einzige Verantwortung eines Entity-Modells sollte das Verhalten sein, das mit der Entität zusammenhängt, die es repräsentiert, es sollte nicht für seine eigene Persistenz verantwortlich sein.
class Person extends Model
{
public $name;
public $birthDate;
protected $preferences;
public function getPreferences() {}
}
class DataStore
{
public function save(Model $model) {}
}
Das ist besser. Das Person-Modell ist wieder nur eine Sache, und das Save-Verhalten wurde zu einem Persistenz-Objekt verschoben. Beachten Sie auch, dass ich nur auf Model getippt habe, nicht auf Person. Wir kommen darauf zurück, wenn wir zu den L- und D-Teilen von SOLID kommen.
3.1.2 O - Open Closed Principle
Es gibt einen tollen Test dafür, der ziemlich genau zusammenfasst, worum es bei diesem Prinzip geht: Denken Sie an eine Funktion, die Sie implementieren sollen, wahrscheinlich die neueste, an der Sie gearbeitet haben oder arbeiten. Können Sie diese Funktion in Ihrem bestehenden Codebasis SOLELY implementieren, indem Sie neue Klassen hinzufügen und keine bestehenden Klassen in Ihrem System ändern? Ihre Konfiguration und Verkabelungscode bekommt ein bisschen Nachsicht, aber in den meisten Systemen ist das überraschend schwierig. Sie müssen sich stark auf polymorphe Dispatch verlassen und die meisten Codebasen sind nicht dafür eingerichtet. Wenn Sie daran interessiert sind, gibt es einen guten Google-Talk auf YouTube über Polymorphismus und Code-Schreiben ohne Ifs, der das weiter ausführt. Als Bonus wird der Talk von Miško Hevery gehalten, den viele als den Erfinder von AngularJs kennen.
3.1.3 L - Liskov Substitution Principle
Dieses Prinzip ist nach Barbara Liskov benannt und lautet wie folgt:
„Objekte in einem Programm sollten durch Instanzen ihrer Untertypen ersetzbar sein, ohne die Korrektheit dieses Programms zu ändern.“
Das klingt alles gut und schön, aber es wird klarer illustriert mit einem Beispiel.
abstract class Shape
{
public function getHeight();
public function setHeight($height);
public function getLength();
public function setLength($length);
}
Das wird unsere grundlegende vierseitige Form darstellen. Nichts Ausgefallenes hier.
class Square extends Shape
{
protected $size;
public function getHeight() {
return $this->size;
}
public function setHeight($height) {
$this->size = $height;
}
public function getLength() {
return $this->size;
}
public function setLength($length) {
$this->size = $length;
}
}
Hier ist unsere erste Form, das Quadrat. Eine ziemlich unkomplizierte Form, oder? Sie können annehmen, dass es einen Konstruktor gibt, in dem wir die Dimensionen festlegen, aber Sie sehen hier aus dieser Implementierung, dass Länge und Höhe immer gleich sein werden. Quadrate sind einfach so.
class Rectangle extends Shape
{
protected $height;
protected $length;
public function getHeight() {
return $this->height;
}
public function setHeight($height) {
$this->height = $height;
}
public function getLength() {
return $this->length;
}
public function setLength($length) {
$this->length = $length;
}
}
Also haben wir hier eine andere Form. Sie hat immer noch die gleichen Methodensignaturen, es ist immer noch eine vierseitige Form, aber was, wenn wir anfangen, sie gegeneinander zu verwenden? Plötzlich, wenn wir die Höhe unserer Shape ändern, können wir nicht mehr annehmen, dass die Länge unserer Shape übereinstimmt. Wir haben den Vertrag verletzt, den wir mit dem Benutzer hatten, als wir ihm unsere Square-Form gaben.
Das ist ein Lehrbuchbeispiel für eine Verletzung des LSP, und wir brauchen ein solches Prinzip, um das Beste aus einem Typsystem zu machen. Sogar Duck Typing wird uns nicht sagen, ob das zugrunde liegende Verhalten anders ist, und da wir das nicht wissen können, ohne dass es bricht, ist es am besten, sicherzustellen, dass es nicht anders ist.
3.1.3 I - Interface Segregation Principle
Dieses Prinzip sagt, dass man vielen kleinen, feingliedrigen Interfaces den Vorzug geben sollte, im Vergleich zu einem großen. Interfaces sollten auf Verhalten basieren und nicht auf „es ist eine dieser Klassen“. Denken Sie an Interfaces, die mit PHP kommen. Traversable, Countable, Serializable, Dinge wie das. Sie werben für Fähigkeiten, die das Objekt besitzt, nicht für das, wovon es erbt. Halten Sie Ihre Interfaces also klein. Sie wollen kein Interface mit 30 Methoden darauf, 3 ist ein viel besseres Ziel.
3.1.4 D - Dependency Inversion Principle
Sie haben das wahrscheinlich an anderen Stellen gehört, die über Dependency Injection gesprochen haben, aber Dependency Inversion und Dependency Injection sind nicht ganz dasselbe. Dependency Inversion ist wirklich nur eine Möglichkeit zu sagen, dass Sie auf Abstraktionen in Ihrem System und nicht auf seine Details angewiesen sein sollten. Was bedeutet das für Sie im Alltag?
Verwenden Sie nicht direkt mysqli_query() überall in Ihrem Code, verwenden Sie stattdessen etwas wie DataStore->query().
Der Kern dieses Prinzips geht eigentlich um Abstraktionen. Es geht mehr darum zu sagen „verwenden Sie einen Datenbank-Adapter“, anstatt auf direkte Aufrufe wie mysqli_query zu vertrauen. Wenn Sie mysqli_query direkt in der Hälfte Ihrer Klassen verwenden, binden Sie alles direkt an Ihre Datenbank. Nichts für oder gegen MySQL hier, aber wenn Sie mysqli_query verwenden, sollte diese Art von niedrigstufigem Detail in nur einem Ort versteckt werden und dann diese Funktionalität über eine generische Wrapper freigegeben werden.
Ich weiß, das ist ein bisschen ein abgedroschener Beispiel, wenn man drüber nachdenkt, weil die Anzahl der Male, in denen Sie Ihren Datenbank-Engine vollständig ändern werden, nachdem Ihr Produkt in Produktion ist, sehr, sehr niedrig ist. Ich habe es gewählt, weil ich dachte, die Leute wären mit der Idee aus ihrem eigenen Code vertraut. Auch, selbst wenn Sie eine Datenbank haben, bei der Sie bleiben, ermöglicht Ihnen dieses abstrakte Wrapper-Objekt, Fehler zu beheben, Verhalten zu ändern oder Funktionen zu implementieren, die Sie sich von Ihrer gewählten Datenbank wünschen. Es macht auch Unit-Testing möglich, wo niedrigstufige Aufrufe das nicht tun würden.
4 Object Calisthenics
Das ist kein voller Einstieg in diese Prinzipien, aber die ersten zwei sind leicht zu merken, bieten guten Wert und können sofort auf fast jeden Codebase angewendet werden.
4.1 Nicht mehr als eine Ebene der Einrückung pro Methode
Das ist eine hilfreiche Möglichkeit, Methoden in kleinere Chunks zu zerlegen, was zu Code führt, der klarer und selbstdokumentierender ist. Je mehr Ebenen der Einrückung Sie haben, desto mehr tut die Methode und desto mehr Zustand müssen Sie im Kopf behalten, während Sie damit arbeiten.
Sofort weiß ich, dass Leute dagegen einwenden werden, aber das ist nur eine Richtlinie/Heuristik, keine harte und schnelle Regel. Ich erwarte nicht, dass jemand PHP_CodeSniffer-Regeln dafür durchsetzt (obwohl Leute das getan haben).
Lassen Sie uns ein schnelles Beispiel durchgehen, wie das aussehen könnte:
public function transformToCsv($data)
{
$csvLines = array();
$csvLines[] = implode(',', array_keys($data[0]));
foreach ($data as $row) {
if (!$row) {
continue;
}
$csvLines[] = implode(',', $row);
}
return $csvLines;
}
Obwohl das technisch korrekter, testbarer usw. Code ist, können wir viel mehr tun, um das klarer zu machen. Wie reduzieren wir die Ebenen der Verschachtelung hier?
Wir wissen, dass wir den Inhalt der foreach-Schleife stark vereinfachen müssen (oder sie ganz entfernen), also beginnen wir da.
if (!$row) {
continue;
}
Das erste Bit ist einfach. Das ignoriert nur leere Zeilen. Wir können diesen gesamten Prozess abkürzen, indem wir eine eingebaute PHP-Funktion verwenden, bevor wir überhaupt zur Schleife kommen.
$data = array_filter($data);
foreach ($data as $row) {
$csvLines[] = implode(',', $row);
}
Jetzt haben wir unsere einzelne Ebene der Verschachtelung. Aber wenn man sich das ansieht, tun wir nichts anderes, als eine Funktion auf jedes Element in einem Array anzuwenden. Wir brauchen nicht einmal die foreach-Schleife dafür.
$data = array_filter($data);
$csvLines = array_map(function($row) {
return implode(',', $row);
}, $data);
Jetzt haben wir gar keine Verschachtelung mehr, und der Code wird wahrscheinlich schneller sein, da wir alle Schleifen mit nativen C-Funktionen anstelle von PHP machen. Wir müssen ein bisschen Trickserei betreiben, um das Komma an implode zu übergeben, also könnte man argumentieren, dass der Stopp beim vorherigen Schritt viel verständlicher ist.
4.2 Versuchen Sie, else nicht zu verwenden
Das behandelt wirklich zwei Hauptideen. Die erste ist mehrere Return-Anweisungen aus einer Methode. Wenn Sie genug Informationen haben, um eine Entscheidung über das Ergebnis der Methode zu treffen, treffen Sie diese Entscheidung und returnen Sie. Die zweite ist eine Idee, die als Guard Clauses bekannt ist. Das sind im Wesentlichen Validierungsprüfungen kombiniert mit frühen Returns, normalerweise ganz oben in einer Methode. Lassen Sie mich Ihnen zeigen, was ich meine.
public function addThreeInts($first, $second, $third) {
if (is_int($first)) {
if (is_int($second)) {
if (is_int($third)) {
$sum = $first + $second + $third;
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
return $sum;
}
Das ist wieder ziemlich unkompliziert, es addiert 3 Integers und gibt das Ergebnis zurück oder null, wenn irgendeiner der Parameter kein Integer ist. Wenn man davon absieht, dass wir all diese Prüfungen in eine einzelne Zeile mit AND-Operatoren kombinieren könnten, denke ich, Sie können sehen, wie die verschachtelte if/else-Struktur den Code schwerer zu folgen macht. Schauen Sie sich stattdessen dieses Beispiel an.
public function addThreeInts($first, $second, $third) {
if (!is_int($first)) {
return null;
}
if (!is_int($second)) {
return null;
}
if (!is_int($third)) {
return null;
}
return $first + $second + $third;
}
Für mich ist dieses Beispiel viel einfacher zu folgen. Hier verwenden wir Guard Clauses, um unsere anfänglichen Annahmen über die Parameter zu überprüfen und die Methode sofort zu verlassen, wenn sie nicht bestehen. Wir haben auch keine Zwischenvariable mehr, um die Summe durch die Methode zu verfolgen. In diesem Fall haben wir überprüft, dass wir bereits auf dem happy path sind, und können einfach tun, wofür wir hier sind. Wieder könnten wir all diese Prüfungen in einem if machen, aber das Prinzip sollte klar sein.
5 Unit-Testing
Unit-Testing ist die Praxis, kleine Tests zu schreiben, die Verhalten in Ihrem Code überprüfen. Sie werden fast immer in derselben Sprache wie der Code (in diesem Fall PHP) geschrieben und sind so schnell gedacht, dass sie jederzeit ausgeführt werden können. Sie sind extrem wertvoll als Tool, um Ihren Code zu verbessern. Neben den offensichtlichen Vorteilen, sicherzustellen, dass Ihr Code tut, was Sie denken, dass er tut, kann Unit-Testing auch sehr nützliches Design-Feedback geben. Wenn ein Stück Code schwer zu testen ist, zeigt das oft Designprobleme auf. Sie geben Ihnen auch ein Sicherheitsnetz gegen Regressionen und ermöglichen es Ihnen, viel öfter zu refactorisieren und Ihren Code zu einer saubereren Design zu entwickeln.
5.1 Tools
Es gibt mehrere Unit-Testing-Tools in PHP, aber mit Abstand das häufigste ist PHPUnit. Sie können es installieren, indem Sie eine PHAR-Datei direkt herunterladen oder es mit Composer installieren. Da wir Composer für alles andere verwenden, zeigen wir diese Methode. Da PHPUnit wahrscheinlich nicht in Produktion deployed wird, können wir es als Dev-Abhängigkeit mit dem folgenden Befehl installieren:
composer require --dev phpunit/phpunit
5.2 Tests sind eine Spezifikation
Die wichtigste Rolle von Unit-Tests in Ihrem Code ist es, eine ausführbare Spezifikation zu bieten, was der Code tun soll. Selbst wenn der Testcode falsch ist oder der Code Fehler hat, ist das Wissen, was das System soll tun, unbezahlbar.
5.3 Schreiben Sie Ihre Tests zuerst
Wenn Sie die Chance hatten, einen Satz Tests zu sehen, der vor dem Code geschrieben wurde, und einen, der nach dem Code geschrieben wurde, sind sie auffallend unterschiedlich. Die „nach“-Tests sind viel mehr mit den Implementierungsdetails der Klasse beschäftigt und stellen sicher, dass sie gute Zeilenumfänge haben, während die „vor“-Tests mehr darum gehen, das gewünschte externe Verhalten zu überprüfen. Das ist wirklich das, was uns mit Unit-Tests interessiert, nämlich sicherzustellen, dass die Klasse das richtige Verhalten zeigt. Auf Implementierung fokussierte Tests machen Refactoring tatsächlich schwieriger, weil sie brechen, wenn die Interna der Klassen ändern, und Sie haben sich gerade die Vorteile der Informationsversteckung in OOP gekostet.
5.4 Was ein guter Unit-Test ausmacht
Gute Unit-Tests teilen viele der folgenden Merkmale:
- Schnell – sollte in Millisekunden laufen.
- Kein Netzwerkzugriff – sollte in der Lage sein, Wireless auszuschalten/unstecken und alle Tests bestehen lassen.
- Begrenzter Dateisystemzugriff – das trägt zur Geschwindigkeit und Flexibilität bei, wenn Code in andere Umgebungen deployed wird.
- Kein Datenbankzugriff – vermeidet kostspielige Setup- und Teardown-Aktivitäten.
- Testen Sie nur eine Sache auf einmal – ein Unit-Test sollte nur einen Grund zum Scheitern haben.
- Gut benannt – siehe 5.2 oben.
- Meist Fake-Objekte – die einzigen „realen“ Objekte in Unit-Tests sollten das Objekt sein, das wir testen, und einfache Value-Objekte. Der Rest sollte eine Form von Test Double sein.
Es gibt Gründe, gegen einige davon zu gehen, aber als allgemeine Richtlinien werden sie Ihnen gut dienen.
5.5 Wenn Testing schmerzhaft ist
Unit-Testing zwingt Sie, den Schmerz eines schlechten Designs vorneweg zu spüren – Michael Feathers
Wenn Sie Unit-Tests schreiben, zwingen Sie sich, die Klasse tatsächlich zu verwenden, um Dinge zu erledigen. Wenn Sie Tests am Ende schreiben oder, schlimmer noch, den Code einfach über die Wand für QA oder wen auch immer werfen, um Tests zu schreiben, bekommen Sie kein Feedback darüber, wie sich die Klasse tatsächlich verhält. Wenn wir Tests schreiben und die Klasse ein echtes Problem ist, finden wir das heraus, während wir sie schreiben, was fast die günstigste Zeit ist, es zu beheben.
Wenn eine Klasse schwer zu testen ist, ist das ein Designfehler. Verschiedene Fehler manifestieren sich auf unterschiedliche Weisen. Wenn Sie eine Menge Mocking machen müssen, hat Ihre Klasse wahrscheinlich zu viele Abhängigkeiten oder Ihre Methoden tun zu viel. Je mehr Setup Sie für jeden Test machen müssen, desto wahrscheinlicher ist es, dass Ihre Methoden zu viel tun. Wenn Sie wirklich komplizierte Test-Szenarien schreiben müssen, um Verhalten auszuführen, tun die Methoden der Klasse wahrscheinlich zu viel. Wenn Sie in eine Menge privater Methoden und Zustände eintauchen müssen, um Dinge zu testen, versucht vielleicht eine andere Klasse herauszukommen. Unit-Testing ist sehr gut darin, „Eisberg-Klassen“ aufzudecken, bei denen 80% dessen, was die Klasse tut, in geschütztem oder privatem Code versteckt ist. Ich war früher ein großer Fan davon, so viel wie möglich geschützt zu machen, aber jetzt habe ich erkannt, dass ich nur meine individuellen Klassen für zu viel verantwortlich gemacht habe, und die echte Lösung war, die Klasse in kleinere Stücke zu zerlegen.
Geschrieben von Brian Fenton – Brian Fenton ist seit 8 Jahren PHP-Entwickler im Mittleren Westen und in der Bay Area, derzeit bei Thismoment. Er konzentriert sich auf Code-Craftsmanship und Designprinzipien. Blog auf www.brianfenton.us, Twitter unter @brianfenton. Wenn er nicht beschäftigt ist, Vater zu sein, genießt er Essen, Bier, Gaming und Lernen.
Learn/security
Sicherheit
Übersicht
Sicherheit ist ein großes Thema bei Webanwendungen. Du möchtest sicherstellen, dass deine Anwendung sicher ist und die Daten deiner Benutzer geschützt sind. Flight bietet eine Reihe von Funktionen, um deine Webanwendungen abzusichern.
Das offizielle skeleton enthält außerdem eine eigene SECURITY.md und eine Security-Header-Middleware, damit KI-Codierungstools (und Menschen) einen zentralen Ort für Geheimnisse, Header und XSS/SQL-Regeln haben – getrennt vom allgemeinen Codierungsstil in AGENTS.md.
Verständnis
Es gibt eine Reihe häufiger Sicherheitsbedrohungen, die du beim Erstellen von Webanwendungen kennen solltest. Zu den häufigsten Bedrohungen gehören:
- Cross Site Request Forgery (CSRF)
- Cross Site Scripting (XSS)
- SQL Injection
- Cross Origin Resource Sharing (CORS)
Templates helfen bei XSS, indem sie die Ausgabe standardmäßig escapen (Twig und Latte tun das; nutze diesen Vorteil). Sessions können bei CSRF helfen, indem sie ein CSRF-Token in der Sitzung des Benutzers speichern, wie unten beschrieben. Die Verwendung von Prepared Statements mit PDO – oder Helfern auf SimplePdo – hilft, SQL-Injection zu verhindern. CORS kann mit einem einfachen Hook vor dem Aufruf von Flight::start() behandelt werden.
Alle diese Methoden arbeiten zusammen, um deine Webanwendungen sicher zu halten. Es sollte dir immer bewusst sein, Sicherheits-Best-Practices zu lernen und zu verstehen. Bitte keinen KI-Assistenten darum, „CSP zu deaktivieren“ oder Header abzuschwächen, nur um eine Seite zu laden, ohne den Kompromiss zu verstehen.
Grundlegende Verwendung
Header
HTTP-Header sind eine der einfachsten Möglichkeiten, deine Webanwendungen abzusichern. Du kannst Header verwenden, um Clickjacking, XSS und andere Angriffe zu verhindern. Es gibt mehrere Möglichkeiten, diese Header zu deiner Anwendung hinzuzufügen.
Zwei großartige Websites, um die Sicherheit deiner Header zu überprüfen, sind securityheaders.com und observatory.mozilla.org. Nachdem du den folgenden Code eingerichtet hast, kannst du mit diesen beiden Websites leicht überprüfen, ob deine Header funktionieren.
Das Skeleton enthält App\Middleware\SecurityHeadersMiddleware (CSP mit einem Nonce pro Anfrage, Frame-Optionen, HSTS und mehr). Bevorzuge es, dies bewusst zu erweitern, anstatt Header abzuschalten.
Manuell hinzufügen
Du kannst diese Header manuell mit der Methode header des Flight\Response-Objekts hinzufügen.
// Setze den X-Frame-Options-Header, um Clickjacking zu verhindern
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
// Setze den Content-Security-Policy-Header, um XSS zu verhindern
// Hinweis: Dieser Header kann sehr komplex werden, daher solltest du
// Beispiele im Internet für deine Anwendung konsultieren
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
// Setze den X-XSS-Protection-Header, um XSS zu verhindern
Flight::response()->header('X-XSS-Protection', '1; mode=block');
// Setze den X-Content-Type-Options-Header, um MIME-Sniffing zu verhindern
Flight::response()->header('X-Content-Type-Options', 'nosniff');
// Setze den Referrer-Policy-Header, um zu steuern, wie viele Referrer-Informationen gesendet werden
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
// Setze den Strict-Transport-Security-Header, um HTTPS zu erzwingen
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
// Setze den Permissions-Policy-Header, um zu steuern, welche Funktionen und APIs verwendet werden können
Flight::response()->header('Permissions-Policy', 'geolocation=()');
Diese können am Anfang deiner routes.php- oder index.php-Dateien hinzugefügt werden.
Als Filter hinzufügen
Du kannst sie auch in einem Filter/Hook wie folgt hinzufügen:
// Füge die Header in einem Filter hinzu
Flight::before('start', function() {
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');
Flight::response()->header("Content-Security-Policy", "default-src 'self'");
Flight::response()->header('X-XSS-Protection', '1; mode=block');
Flight::response()->header('X-Content-Type-Options', 'nosniff');
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
Flight::response()->header('Permissions-Policy', 'geolocation=()');
});
Als Middleware hinzufügen
Du kannst sie auch als Middleware-Klasse hinzufügen, die die größte Flexibilität bietet, auf welche Routen dies angewendet wird. Im Allgemeinen sollten diese Header auf alle HTML- und API-Antworten angewendet werden.
Skeleton-Stil Pfad und Namespace (Ordner-Schreibweise entspricht App\Middleware):
// app/Middleware/SecurityHeadersMiddleware.php
namespace App\Middleware;
use flight\Engine;
class SecurityHeadersMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
$response = $this->app->response();
// Bevorzuge ein CSP-Nonce aus dem Bootstrap, wenn du Inline-Skripte hast (Skeleton setzt csp_nonce)
$nonce = $this->app->get('csp_nonce');
$csp = $nonce
? "default-src 'self'; script-src 'self' 'nonce-{$nonce}'; style-src 'self' 'nonce-{$nonce}'"
: "default-src 'self'";
$response->header('X-Frame-Options', 'SAMEORIGIN');
$response->header('Content-Security-Policy', $csp);
$response->header('X-XSS-Protection', '1; mode=block');
$response->header('X-Content-Type-Options', 'nosniff');
$response->header('Referrer-Policy', 'no-referrer-when-downgrade');
$response->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
$response->header('Permissions-Policy', 'geolocation=()');
}
}
// app/config/routes.php — leere String-Gruppe = globale Middleware für alle Routen
use App\Middleware\SecurityHeadersMiddleware;
use flight\net\Router;
$router->group('', function (Router $router) {
$router->get('/users', [ \App\Controller\UserController::class, 'getUsers' ]);
// weitere Routen
}, [SecurityHeadersMiddleware::class]);
Ältere Projekte verwenden möglicherweise weiterhin app/middlewares und app\middlewares; das funktioniert, wenn die Ordner übereinstimmen. Neue Skeleton-Apps verwenden app/Middleware/ und App\Middleware. Siehe Autoloading.
Cross-Site-Request-Forgery (CSRF)
Cross-Site-Request-Forgery (CSRF) ist eine Angriffsart, bei der eine bösartige Website den Browser eines Benutzers dazu bringen kann, eine Anfrage an deine Website zu senden. Dies kann verwendet werden, um Aktionen auf deiner Website ohne das Wissen des Benutzers auszuführen. Flight bietet keinen eingebauten CSRF-Schutzmechanismus, aber du kannst deinen eigenen einfach mit Middleware implementieren.
Einrichtung
Zuerst musst du ein CSRF-Token generieren und es in der Sitzung des Benutzers speichern. Du kannst dieses Token dann in deinen Formularen verwenden und es beim Absenden des Formulars überprüfen. Wir verwenden das Plugin flightphp/session zur Verwaltung von Sitzungen.
// Generiere ein CSRF-Token und speichere es in der Sitzung des Benutzers
// (angenommen, du hast ein Sitzungsobjekt erstellt und an Flight angehängt)
// Weitere Informationen findest du in der Sitzungsdokumentation
Flight::register('session', flight\Session::class);
// Du musst nur ein einziges Token pro Sitzung generieren (damit es über mehrere Tabs und Anfragen für denselben Benutzer funktioniert)
if(Flight::session()->get('csrf_token') === null) {
Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
Verwenden des standardmäßigen PHP-Flight-Templates
<!-- Verwende das CSRF-Token in deinem Formular -->
<form method="post">
<input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
<!-- andere Formularfelder -->
</form>
Verwenden von Twig (Skeleton-Standard)
Registriere eine Twig-Funktion oder übergib das Token an jede Formularansicht. Minimalbeispiel mit einem Global + Formularfeld:
// Beim Konfigurieren von Twig (z. B. services.php)
$twig->addGlobal('csrf_token', $app->session()->get('csrf_token'));
{# app/views/form.twig #}
<form method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
{# andere Felder #}
</form>
Verwenden von Latte
Du kannst auch eine benutzerdefinierte Funktion einrichten, um das CSRF-Token in deinen Latte-Templates auszugeben.
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// weitere Konfigurationen ...
// Setze eine benutzerdefinierte Funktion, um das CSRF-Token auszugeben
$latte->addFunction('csrf', function() {
$csrfToken = Flight::session()->get('csrf_token');
return new \Latte\Runtime\Html('<input type="hidden" name="csrf_token" value="' . $csrfToken . '">');
});
$latte->render($finalPath, $data, $block);
});
Und jetzt kannst du in deinen Latte-Templates die Funktion csrf() verwenden, um das CSRF-Token auszugeben.
<form method="post">
{csrf()}
<!-- andere Formularfelder -->
</form>
CSRF-Token überprüfen
Du kannst das CSRF-Token mit mehreren Methoden überprüfen.
Middleware
// app/Middleware/CsrfMiddleware.php
namespace App\Middleware;
use flight\Engine;
class CsrfMiddleware
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function before(array $params): void
{
if($this->app->request()->method == 'POST') {
$token = $this->app->request()->data->csrf_token;
if($token !== $this->app->session()->get('csrf_token')) {
$this->app->halt(403, 'Invalid CSRF token');
}
}
}
}
// routes.php
use App\Middleware\CsrfMiddleware;
$router->group('', function ($router) {
$router->get('/users', [ \App\Controller\UserController::class, 'getUsers' ]);
// weitere Routen
}, [CsrfMiddleware::class]);
Ereignisfilter
// Diese Middleware prüft, ob die Anfrage eine POST-Anfrage ist, und wenn ja, prüft sie, ob das CSRF-Token gültig ist
Flight::before('start', function() {
if(Flight::request()->method == 'POST') {
// Erfasse das CSRF-Token aus den Formularwerten
$token = Flight::request()->data->csrf_token;
if($token !== Flight::session()->get('csrf_token')) {
Flight::halt(403, 'Invalid CSRF token');
// oder für eine JSON-Antwort
Flight::jsonHalt(['error' => 'Invalid CSRF token'], 403);
}
}
});
Cross-Site-Scripting (XSS)
Cross-Site-Scripting (XSS) ist eine Angriffsart, bei der eine bösartige Formulareingabe Code in deine Website einschleusen kann. Die meisten dieser Möglichkeiten stammen aus Formularwerten, die deine Endbenutzer ausfüllen. Du solltest die Ausgabe deiner Benutzer niemals vertrauen! Gehe immer davon aus, dass alle die besten Hacker der Welt sind. Sie können bösartiges JavaScript oder HTML in deine Seite einschleusen. Dieser Code kann verwendet werden, um Informationen von deinen Benutzern zu stehlen oder Aktionen auf deiner Website auszuführen. Mit der View-Klasse von Flight oder einer Template-Engine wie Twig oder Latte kannst du die Ausgabe leicht escapen, um XSS-Angriffe zu verhindern.
// Nehmen wir an, der Benutzer ist clever und versucht, dies als seinen Namen zu verwenden
$name = '<script>alert("XSS")</script>';
// Dies wird die Ausgabe escapen
Flight::view()->set('name', $name);
// Dies wird ausgegeben: <script>alert("XSS")</script>
// Twig (Skeleton-Standard) und Latte escapen standardmäßig automatisch – bevorzuge sie gegenüber rohem PHP-echo
Flight::render('template', ['name' => $name]);
// Twig: {{ name }} → maskiert
// Vermeide |raw / unescaped Ausgabe, es sei denn, der Inhalt ist vollständig vertrauenswürdig
SQL-Injection
SQL-Injection ist eine Angriffsart, bei der ein böswilliger Benutzer SQL-Code in deine Datenbank einschleusen kann. Dies kann verwendet werden, um Informationen aus deiner Datenbank zu stehlen oder Aktionen in deiner Datenbank auszuführen. Auch hier solltest du Eingaben von deinen Benutzern niemals vertrauen! Gehe immer davon aus, dass sie auf Blut aus sind. Verwende Prepared Statements – die Helfer von SimplePdo machen dies zum Standardweg.
// Angenommen, du hast Flight::db() als SimplePdo registriert (oder SimplePdo in den Controller injiziert)
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();
// SimplePdo (bevorzugt) — Einzeiler mit gebundenen Parametern
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);
// Gleiche Idee mit ?-Platzhaltern
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);
In Controllern im Skeleton-Stil solltest du die Konstruktor-Injektion von SimplePdo gegenüber Flight::db() bevorzugen, damit Tests und KI-generierter Code konsistent bleiben (DIC).
Unsicheres Beispiel
Das Folgende zeigt, warum wir SQL-Prepared Statements verwenden, um vor harmlosen Beispielen wie dem folgenden zu schützen:
// Endbenutzer füllt ein Webformular aus.
// Für den Wert des Formulars gibt der Hacker so etwas ein:
$username = "' OR 1=1; -- ";
$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// Nachdem die Abfrage erstellt wurde, sieht sie so aus
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5
// Es sieht seltsam aus, aber es ist eine gültige Abfrage, die funktionieren wird. Tatsächlich
// ist es eine sehr häufige SQL-Injection-Angriffsmethode, die alle Benutzer zurückgibt.
var_dump($users); // dies wird alle Benutzer in der Datenbank ausgeben, nicht nur den einen einzelnen Benutzernamen
Geheimnisse und Konfiguration
- Lege Geheimnisse in
.env(oder in die echte Umgebung) ab, nicht in eingecheckteconfig.php-Beispiele. - Skeleton-Regel: Literale Standardwerte in
config.php; Env beim Bootstrap zusammenführen; lies$_ENVnicht in Controllern – injiziere stattdessen die Konfiguration. Siehe Configuration. - Commite niemals API-Schlüssel, Datenbankpasswörter oder Sitzungsverschlüsselungsschlüssel. Weise KI-Tools auf
SECURITY.mdhin, damit sie keine unsicheren Abkürzungen erfinden.
JSONP-Callback-Validierung
Wenn du die Methode Flight::jsonp() von Flight verwendest, beachte, dass Flight den Namen des JSONP-Callback-Parameters gegen einen strengen Allowlist-Regex prüft (/^[A-Za-z_$][\w$.]{0,127}$/). Jeder Callback-Name, der diesem Muster nicht entspricht, führt dazu, dass Flight eine Ausnahme auslöst und so die Injektion von beliebigem JavaScript über einen böswilligen Callback-Wert verhindert.
Diese Validierung ist eingebaut und erfordert keine zusätzliche Konfiguration, aber es ist hilfreich, sie zu kennen, wenn unerwartete Fehler von JSONP-Endpunkten debuggt werden.
CORS
Cross-Origin Resource Sharing (CORS) ist ein Mechanismus, der es ermöglicht, viele Ressourcen (z. B. Schriftarten, JavaScript usw.) auf einer Webseite von einer anderen Domain anzufordern, die außerhalb der Domain liegt, von der die Ressource stammt. Flight hat keine eingebaute Funktion, aber dies kann leicht mit einem Hook behandelt werden, der vor dem Aufruf der Methode Flight::start() ausgeführt wird.
// app/Utils/CorsUtil.php (Skeleton: PascalCase-Ordner Utils → App\Utils)
namespace App\Utils;
use flight\Engine;
class CorsUtil
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function set(array $params = []): void
{
$request = $this->app->request();
$response = $this->app->response();
if ($request->getVar('HTTP_ORIGIN') !== '') {
$this->allowOrigins();
$response->header('Access-Control-Allow-Credentials', 'true');
$response->header('Access-Control-Max-Age', '86400');
}
if ($request->method === 'OPTIONS') {
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_METHOD') !== '') {
$response->header(
'Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
);
}
if ($request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS') !== '') {
$response->header(
"Access-Control-Allow-Headers",
$request->getVar('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
);
}
$response->status(200);
$response->send();
exit;
}
}
private function allowOrigins(): void
{
// Passe hier deine erlaubten Hosts an.
$allowed = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:4200',
'http://localhost:8080',
'http://localhost:8100',
];
$request = $this->app->request();
if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
$response = $this->app->response();
$response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
}
}
}
// Bootstrap / Routen — vor dem Start ausführen
$app = Flight::app();
$cors = new \App\Utils\CorsUtil($app);
$app->before('start', [ $cors, 'set' ]);
Härtung der Flight-Konfiguration
Flight legt mehrere Engine-Einstellungen offen, die direkte Sicherheitsauswirkungen haben. Diese richtig zu setzen, ist einer der einfachsten Wege, deine Anwendung zu härten.
flight.allow_method_override
Standardmäßig erlaubt Flight Clients, die HTTP-Methode einer Anfrage entweder über den X-HTTP-Method-Override-Header oder ein _method-Feld im POST-Body zu überschreiben. Das ist zwar praktisch für HTML-Formulare, die nur GET/POST senden können, kann aber gefährlich sein, wenn du es nicht erwartest – ein Angreifer könnte über ein normales Formular DELETE- oder PUT-Anfragen fälschen.
Wenn deine Anwendung sich nicht auf dieses Verhalten verlässt (z. B. wenn du eine API baust, die von modernen Clients oder JavaScript-Frontends konsumiert wird, die jedes HTTP-Verb senden können), solltest du es deaktivieren:
// In deiner index.php- oder Bootstrap-Datei, vor Flight::start()
Flight::set('flight.allow_method_override', false);
Der Standardwert ist true für die Abwärtskompatibilität, aber es wird dringend empfohlen, ihn auf false zu setzen für jede Anwendung, die die Überschreibungsfunktion nicht explizit benötigt.
flight.debug
Flight hat eine Einstellung flight.debug, die steuert, ob detaillierte Fehlerinformationen (Ausnahmemeldung, Code und vollständiger Stack-Trace) im Browser angezeigt werden, wenn eine unbehandelte Ausnahme auftritt. Der Standardwert ist false, was bedeutet, dass nur eine allgemeine Meldung 500 Internal Server Error angezeigt wird – keine internen Details werden an den Client weitergegeben.
Aktiviere dies niemals auf einem Produktionsserver. Verwende es nur lokal oder in einer Staging-Umgebung:
// Nur für die lokale Entwicklung sicher — NIEMALS in der Produktion
Flight::set('flight.debug', true);
Wenn flight.debug false ist (Standard), kannst du Fehler dennoch erfassen, indem du flight.log_errors aktivierst:
// Protokolliere Fehler serverseitig, ohne sie dem Client zu zeigen
Flight::set('flight.debug', false);
Flight::set('flight.log_errors', true);
Empfohlene Produktionskonfiguration
// index.php oder aus der App-Konfiguration / Bootstrap übernommen
Flight::set('flight.allow_method_override', false);
Flight::set('flight.debug', false);
Flight::set('flight.log_errors', true);
Fehlerbehandlung
Verstecke sensible Fehlerdetails in der Produktion, um zu vermeiden, dass Informationen an Angreifer gelangen. In der Produktion protokolliere Fehler, anstatt sie anzuzeigen, mit display_errors auf 0 gesetzt.
// In deiner bootstrap.php oder index.php
// Füge dies zu deiner app/config/config.php hinzu
$environment = ENVIRONMENT;
if ($environment === 'production') {
ini_set('display_errors', 0); // Deaktiviere die Fehleranzeige
ini_set('log_errors', 1); // Protokolliere stattdessen Fehler
ini_set('error_log', '/path/to/error.log');
}
// In deinen Routen oder Controllern
// Verwende Flight::halt() für kontrollierte Fehlerantworten
Flight::halt(403, 'Access denied');
Eingabebereinigung
Vertraue niemals Benutzereingaben. Bereinige sie mit filter_var, bevor du sie verarbeitest, um zu verhindern, dass bösartige Daten eingeschleust werden. Bevorzuge es, Eingaben über $app->request() (oder Flight::request()) zu lesen, anstatt rohe $_GET / $_POST im Anwendungscode zu verwenden.
// Nehmen wir an, eine $_POST-Anfrage mit $_POST['input'] und $_POST['email']
// Bereinige eine Zeichenketteneingabe
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Bereinige eine E-Mail
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);
Passwort-Hashing
Speichere Passwörter sicher und verifiziere sie sicher mit den eingebauten PHP-Funktionen wie password_hash und password_verify. Passwörter sollten niemals im Klartext gespeichert oder mit reversiblen Methoden verschlüsselt werden. Hashing stellt sicher, dass die tatsächlichen Passwörter geschützt bleiben, selbst wenn deine Datenbank kompromittiert wird.
$password = Flight::request()->data->password;
// Hashe ein Passwort beim Speichern (z. B. bei der Registrierung)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
// Verifiziere ein Passwort (z. B. beim Login)
if (password_verify($password, $stored_hash)) {
// Passwort stimmt überein
}
Ratenbegrenzung
Schütze vor Brute-Force-Angriffen oder Denial-of-Service-Angriffen, indem du die Anfrageraten mit einem Cache begrenzt.
// Angenommen, du hast flightphp/cache installiert und registriert
// Verwendung von flightphp/cache in einem Filter
Flight::before('start', function() {
$cache = Flight::cache();
$ip = Flight::request()->ip;
$key = "rate_limit_{$ip}";
$attempts = (int) $cache->retrieve($key);
if ($attempts >= 10) {
Flight::halt(429, 'Too many requests');
}
$cache->set($key, $attempts + 1, 60); // Nach 60 Sekunden zurücksetzen
});
Siehe auch
- Sessions – So verwaltest du Benutzersitzungen sicher.
- Templates – Twig/Latte automatisches Escaping und XSS.
- SimplePdo – Datenbankhelfer mit Prepared Statements.
- PdoWrapper – Veraltet; verwende SimplePdo für neuen Code.
- Middleware – So verwendest du Middleware, um das Hinzufügen von Sicherheits-Headern zu vereinfachen.
- Configuration –
.envvs. literale Konfiguration, Produktions-Flags. - AI & Developer Experience – Halte die Sicherheitsrichtlinie in
SECURITY.mdfür Agenten bereit. - Responses – So passt du HTTP-Antworten mit sicheren Headern an.
- Requests – So verarbeitest und bereinigst du Benutzereingaben.
- filter_var – PHP-Funktion zur Eingabebereinigung.
- password_hash – PHP-Funktion für sicheres Passwort-Hashing.
- password_verify – PHP-Funktion zur Verifizierung von Passwort-Hashes.
Fehlerbehebung
- Siehe den Abschnitt „Siehe auch“ oben für Informationen zur Fehlerbehebung bei Problemen mit Komponenten des Flight-Frameworks.
- Wenn CSP deine Skripte blockiert, füge ein Nonce hinzu (Skeleton-Muster) oder erlaube bestimmte Ursprünge in einer Allowlist – setze
script-src *nicht ohne einen Plan.
Changelog
- Docs – Skeleton
App\Middleware, Twig CSRF/XSS-Hinweise, SimplePdo, Geheimnisse/.envundSECURITY.mdfür KI-freundliche Projekte. - v3.18.1 – Abschnitt „Härtung der Flight-Konfiguration“ hinzugefügt, der
flight.allow_method_override,flight.debugund JSONP-Callback-Validierung abdeckt. - v3.1.0 – Abschnitte zu CORS, Fehlerbehandlung, Eingabebereinigung, Passwort-Hashing und Ratenbegrenzung hinzugefügt.
- v2.0 – Escaping für Standardansichten hinzugefügt, um XSS zu verhindern.
Learn/routing
Routing
Überblick
Routing in Flight PHP ordnet URL-Muster Callback-Funktionen oder Klassenmethoden zu und ermöglicht so eine schnelle und einfache Anfrageverarbeitung. Es ist auf minimalen Overhead ausgelegt, anfängerfreundlich und ohne externe Abhängigkeiten erweiterbar.
Verständnis
Routing ist der Kernmechanismus, der HTTP-Anfragen mit deiner Anwendungslogik in Flight verbindet. Durch die Definition von Routen legst du fest, wie verschiedene URLs bestimmten Code auslösen – sei es durch Funktionen, Klassenmethoden oder Controller-Aktionen. Das Routing-System von Flight ist flexibel und unterstützt grundlegende Muster, benannte Parameter, reguläre Ausdrücke sowie erweiterte Funktionen wie Dependency Injection und ressourcenorientiertes Routing. Dieser Ansatz hält deinen Code organisiert und wartungsfreundlich, bleibt dabei schnell und einfach für Anfänger und ist für fortgeschrittene Benutzer erweiterbar.
Hinweis: Du möchtest mehr über Routing verstehen? Schau dir die Seite "Warum ein Framework?" für eine ausführlichere Erklärung an.
Grundlegende Verwendung
Eine einfache Route definieren
Grundlegendes Routing in Flight erfolgt durch das Abgleichen eines URL-Musters mit einer Callback-Funktion oder einem Array aus Klasse und Methode.
Flight::route('/', function(){
echo 'hello world!';
});
Routen werden in der Reihenfolge abgeglichen, in der sie definiert sind. Die erste Route, die eine Anfrage matcht, wird aufgerufen.
Funktionen als Callbacks verwenden
Der Callback kann jedes aufrufbare Objekt sein. Du kannst also eine normale Funktion verwenden:
function hello() {
echo 'hello world!';
}
Flight::route('/', 'hello');
Klassen und Methoden als Controller verwenden
Du kannst auch eine Methode (statisch oder nicht statisch) einer Klasse verwenden:
class GreetingController {
public function hello() {
echo 'hello world!';
}
}
Flight::route('/', [ 'GreetingController','hello' ]);
// oder
Flight::route('/', [ GreetingController::class, 'hello' ]); // bevorzugte Methode
// oder
Flight::route('/', [ 'GreetingController::hello' ]);
// oder
Flight::route('/', [ 'GreetingController->hello' ]);
Oder indem du zuerst ein Objekt erstellst und dann die Methode aufrufst:
use flight\Engine;
// GreetingController.php
class GreetingController
{
protected Engine $app
public function __construct(Engine $app) {
$this->app = $app;
$this->name = 'John Doe';
}
public function hello() {
echo "Hello, {$this->name}!";
}
}
// index.php
$app = Flight::app();
$greeting = new GreetingController($app);
Flight::route('/', [ $greeting, 'hello' ]);
Hinweis: Standardmäßig wird die Klasse
flight\Engineimmer injiziert, wenn ein Controller innerhalb des Frameworks aufgerufen wird, es sei denn, du legst dies über einen Dependency-Injection-Container fest.
Methodenspezifisches Routing
Standardmäßig werden Routenmuster gegen alle Anfragemethoden abgeglichen. Du kannst auf bestimmte Methoden reagieren, indem du einen Bezeichner vor die URL setzt.
Flight::route('GET /', function () {
echo 'Ich habe eine GET-Anfrage erhalten.';
});
Flight::route('POST /', function () {
echo 'Ich habe eine POST-Anfrage erhalten.';
});
// Du kannst Flight::get() nicht für Routen verwenden, da dies eine Methode
// zum Abrufen von Variablen ist, nicht zum Erstellen einer Route.
Flight::post('/', function() { /* Code */ });
Flight::patch('/', function() { /* Code */ });
Flight::put('/', function() { /* Code */ });
Flight::delete('/', function() { /* Code */ });
Du kannst auch mehrere Methoden auf einen einzelnen Callback abbilden, indem du ein |-Trennzeichen verwendest:
Flight::route('GET|POST /', function () {
echo 'Ich habe entweder eine GET- oder eine POST-Anfrage erhalten.';
});
Spezielle Behandlung für HEAD- und OPTIONS-Anfragen
Flight bietet eine integrierte Behandlung für HEAD- und OPTIONS-HTTP-Anfragen:
HEAD-Anfragen
- HEAD-Anfragen werden wie
GET-Anfragen behandelt, aber Flight entfernt automatisch den Antwort-Body, bevor er an den Client gesendet wird. - Das bedeutet, du kannst eine Route für
GETdefinieren, und HEAD-Anfragen an dieselbe URL geben nur Header zurück (keinen Inhalt), wie es HTTP-Standards erwarten.
Flight::route('GET /info', function() {
echo 'Dies ist eine Information!';
});
// Eine HEAD-Anfrage an /info gibt dieselben Header zurück, aber keinen Body.
OPTIONS-Anfragen
OPTIONS-Anfragen werden von Flight automatisch für jede definierte Route behandelt.
- Wenn eine OPTIONS-Anfrage empfangen wird, antwortet Flight mit einem
204 No Content-Status und einemAllow-Header, der alle unterstützten HTTP-Methoden für diese Route auflistet. - Du musst keine separate Route für OPTIONS definieren.
// Für eine Route, die definiert ist als:
Flight::route('GET|POST /users', function() { /* ... */ });
// Eine OPTIONS-Anfrage an /users antwortet mit:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS
Das Router-Objekt verwenden
Zusätzlich kannst du das Router-Objekt abrufen, das einige Hilfsmethoden für dich bereithält:
$router = Flight::router();
// bildet alle Methoden ab, genau wie Flight::route()
$router->map('/', function() {
echo 'hello world!';
});
// GET-Anfrage
$router->get('/users', function() {
echo 'users';
});
$router->post('/users', function() { /* Code */});
$router->put('/users/update/@id', function() { /* Code */});
$router->delete('/users/@id', function() { /* Code */});
$router->patch('/users/@id', function() { /* Code */});
Reguläre Ausdrücke (Regex)
Du kannst reguläre Ausdrücke in deinen Routen verwenden:
Flight::route('/user/[0-9]+', function () {
// Dies matcht /user/1234
});
Obwohl diese Methode verfügbar ist, wird empfohlen, benannte Parameter oder benannte Parameter mit regulären Ausdrücken zu verwenden, da sie lesbarer und leichter zu warten sind.
Benannte Parameter
Du kannst benannte Parameter in deinen Routen angeben, die an deine Callback-Funktion übergeben werden. Dies dient eher der Lesbarkeit der Route als allem anderen. Bitte beachte den wichtigen Hinweis im folgenden Abschnitt.
Flight::route('/@name/@id', function (string $name, string $id) {
echo "hello, $name ($id)!";
});
Du kannst auch reguläre Ausdrücke mit deinen benannten Parametern einbinden, indem du das :-Trennzeichen verwendest:
Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
// Dies matcht /bob/123
// Aber nicht /bob/12345
});
Hinweis: Das Abgleichen von Regex-Gruppen
()mit Positionsparametern wird nicht unterstützt. Beispiel::'\(
Wichtiger Hinweis
Obwohl es im obigen Beispiel so aussieht, als ob @name direkt mit der Variable $name verbunden ist, ist das nicht der Fall. Die Reihenfolge der Parameter in der Callback-Funktion bestimmt, was an sie übergeben wird. Wenn du die Reihenfolge der Parameter in der Callback-Funktion vertauschst, werden auch die Variablen vertauscht. Hier ist ein Beispiel:
Flight::route('/@name/@id', function (string $id, string $name) {
echo "hello, $name ($id)!";
});
Und wenn du die folgende URL aufrufst: /bob/123, wäre die Ausgabe hello, 123 (bob)!.
Sei vorsichtig, wenn du deine Routen und Callback-Funktionen einrichtest!
Optionale Parameter
Du kannst benannte Parameter angeben, die für den Abgleich optional sind, indem du Segmente in Klammern setzt.
Flight::route(
'/blog(/@year(/@month(/@day)))',
function(?string $year, ?string $month, ?string $day) {
// Dies matcht die folgenden URLs:
// /blog/2012/12/10
// /blog/2012/12
// /blog/2012
// /blog
}
);
Alle optionalen Parameter, die nicht gematcht werden, werden als NULL übergeben.
Wildcard-Routing
Der Abgleich erfolgt nur auf einzelnen URL-Segmenten. Wenn du mehrere Segmente abgleichen möchtest, kannst du den *-Platzhalter verwenden.
Flight::route('/blog/*', function () {
// Dies matcht /blog/2000/02/01
});
Um alle Anfragen an einen einzelnen Callback weiterzuleiten, kannst du Folgendes tun:
Flight::route('*', function () {
// Etwas tun
});
404 Not Found Handler
Standardmäßig sendet Flight eine sehr einfache und schlichte HTTP 404 Not Found-Antwort, wenn eine URL nicht gefunden werden kann.
Wenn du eine individuellere 404-Antwort wünschst, kannst du deine eigene notFound-Methode mappen:
Flight::map('notFound', function() {
$url = Flight::request()->url;
// Du könntest auch Flight::render() mit einer eigenen Vorlage verwenden.
$output = <<<HTML
<h1>Mein benutzerdefinierter 404 Not Found</h1>
<h3>Die von dir angeforderte Seite {$url} konnte nicht gefunden werden.</h3>
HTML;
$this->response()
->clearBody()
->status(404)
->write($output)
->send();
});
Method Not Found Handler
Standardmäßig sendet Flight eine sehr einfache und schlichte HTTP 405 Method Not Allowed-Antwort (z. B. Method Not Allowed. Zulässige Methoden sind: GET, POST), wenn eine URL gefunden wird, aber die Methode nicht erlaubt ist. Es wird auch ein Allow-Header mit den zulässigen Methoden für diese URL mitgesendet.
Wenn du eine individuellere 405-Antwort wünschst, kannst du deine eigene methodNotFound-Methode mappen:
use flight\net\Route;
Flight::map('methodNotFound', function(Route $route) {
$url = Flight::request()->url;
$methods = implode(', ', $route->methods);
// Du könntest auch Flight::render() mit einer eigenen Vorlage verwenden.
$output = <<<HTML
<h1>Mein benutzerdefinierter 405 Method Not Allowed</h1>
<h3>Die von dir angeforderte Methode für {$url} ist nicht erlaubt.</h3>
<p>Zulässige Methoden sind: {$methods}</p>
HTML;
$this->response()
->clearBody()
->status(405)
->setHeader('Allow', $methods)
->write($output)
->send();
});
Fortgeschrittene Verwendung
Dependency Injection in Routen
Wenn du Dependency Injection über einen Container (PSR-11, PHP-DI, Dice, etc.) verwenden möchtest, sind die einzigen Routentypen, bei denen das verfügbar ist, entweder das direkte Erstellen des Objekts selbst und die Verwendung des Containers zum Erstellen deines Objekts, oder du kannst Strings verwenden, um die Klasse und Methode zu definieren, die aufgerufen werden sollen. Weitere Informationen findest du auf der Seite Dependency Injection.
Hier ist ein kurzes Beispiel:
use flight\database\SimplePdo;
// Greeting.php
class Greeting
{
protected SimplePdo $db;
public function __construct(SimplePdo $db) {
$this->db = $db;
}
public function hello(int $id) {
// etwas mit $this->db tun
$name = $this->db->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
echo "Hallo, Welt! Mein Name ist {$name}!";
}
}
// index.php
// Richte den Container mit den benötigten Parametern ein
// Weitere Informationen zu PSR-11 findest du auf der Dependency-Injection-Seite
$dice = new \Dice\Dice();
// Vergiss nicht, die Variable mit '$dice = ' neu zuzuweisen!!!!!
$dice = $dice->addRule(SimplePdo::class, [
'shared' => true,
'constructParams' => [
'mysql:host=localhost;dbname=test',
'root',
'password'
]
]);
// Registriere den Container-Handler
Flight::registerContainerHandler(function($class, $params) use ($dice) {
return $dice->create($class, $params);
});
// Routen wie gewohnt
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// oder
Flight::route('/hello/@id', 'Greeting->hello');
// oder
Flight::route('/hello/@id', 'Greeting::hello');
Flight::start();
Ausführung an die nächste Route übergeben
Veraltet
Du kannst die Ausführung an die nächste passende Route übergeben, indem du true aus deiner Callback-Funktion zurückgibst.
Flight::route('/user/@name', function (string $name) {
// Eine Bedingung prüfen
if ($name !== "Bob") {
// Mit der nächsten Route fortfahren
return true;
}
});
Flight::route('/user/*', function () {
// Dies wird aufgerufen
});
Es wird nun empfohlen, Middleware zu verwenden, um komplexe Anwendungsfälle wie diesen zu behandeln.
Route-Aliase
Durch die Vergabe eines Alias für eine Route kannst du diesen Alias später in deiner App dynamisch aufrufen, um ihn im Code generieren zu lassen (z. B. einen Link in einem HTML-Template oder die Erstellung einer Redirect-URL).
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// oder
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
// später irgendwo im Code
class UserController {
public function update() {
// Code zum Speichern des Benutzers...
$id = $user['id']; // z. B. 5
$redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // gibt '/users/5' zurück
Flight::redirect($redirectUrl);
}
}
Dies ist besonders hilfreich, wenn sich deine URL ändert. Im obigen Beispiel nehmen wir an, dass users zu /admin/users/@id verschoben wurde.
Dank des Alias für die Route musst du nicht mehr alle alten URLs in deinem Code suchen und ändern, da der Alias nun /admin/users/5 zurückgibt, wie im obigen Beispiel.
Route-Aliase funktionieren auch in Gruppen:
Flight::group('/users', function() {
Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// oder
Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});
Routeninformationen untersuchen
Wenn du die Informationen der passenden Route untersuchen möchtest, gibt es zwei Möglichkeiten:
- Du kannst die Eigenschaft
executedRouteauf dem ObjektFlight::router()verwenden. - Du kannst darum bitten, dass das Routenobjekt an deinen Callback übergeben wird, indem du
trueals dritten Parameter in der Routenmethode übergibst. Das Routenobjekt ist immer der letzte Parameter, der an deine Callback-Funktion übergeben wird.
executedRoute
Flight::route('/', function() {
$route = Flight::router()->executedRoute;
// Etwas mit $route tun
// Array der abgeglichenen HTTP-Methoden
$route->methods;
// Array der benannten Parameter
$route->params;
// Passender regulärer Ausdruck
$route->regex;
// Enthält den Inhalt aller '*' im URL-Muster
$route->splat;
// Zeigt den URL-Pfad....falls du ihn wirklich brauchst
$route->pattern;
// Zeigt, welche Middleware dieser Route zugewiesen ist
$route->middleware;
// Zeigt den Alias, der dieser Route zugewiesen ist
$route->alias;
});
Hinweis: Die Eigenschaft
executedRoutewird nur gesetzt, nachdem eine Route ausgeführt wurde. Wenn du versuchst, darauf vor der Ausführung einer Route zuzugreifen, ist sieNULL. Du kannstexecutedRouteauch in Middleware verwenden!
true an die Routendefinition übergeben
Flight::route('/', function(\flight\net\Route $route) {
// Array der abgeglichenen HTTP-Methoden
$route->methods;
// Array der benannten Parameter
$route->params;
// Passender regulärer Ausdruck
$route->regex;
// Enthält den Inhalt aller '*' im URL-Muster
$route->splat;
// Zeigt den URL-Pfad....falls du ihn wirklich brauchst
$route->pattern;
// Zeigt, welche Middleware dieser Route zugewiesen ist
$route->middleware;
// Zeigt den Alias, der dieser Route zugewiesen ist
$route->alias;
}, true);// <-- Dieser true-Parameter bewirkt das
Routen-Gruppierung und Middleware
Es kann Zeiten geben, in denen du zusammengehörige Routen gruppieren möchtest (z. B. /api/v1).
Du kannst dies mit der group-Methode tun:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Matcht /api/v1/users
});
Flight::route('/posts', function () {
// Matcht /api/v1/posts
});
});
Du kannst sogar Gruppen von Gruppen verschachteln:
Flight::group('/api', function () {
Flight::group('/v1', function () {
// Flight::get() ruft Variablen ab, es setzt keine Route! Siehe Objektkontext unten
Flight::route('GET /users', function () {
// Matcht GET /api/v1/users
});
Flight::post('/posts', function () {
// Matcht POST /api/v1/posts
});
Flight::put('/posts/1', function () {
// Matcht PUT /api/v1/posts
});
});
Flight::group('/v2', function () {
// Flight::get() ruft Variablen ab, es setzt keine Route! Siehe Objektkontext unten
Flight::route('GET /users', function () {
// Matcht GET /api/v2/users
});
});
});
Gruppierung mit Objektkontext
Du kannst die Routengruppierung weiterhin mit dem Engine-Objekt auf folgende Weise verwenden:
$app = Flight::app();
$app->group('/api/v1', function (Router $router) {
// verwende die Variable $router
$router->get('/users', function () {
// Matcht GET /api/v1/users
});
$router->post('/posts', function () {
// Matcht POST /api/v1/posts
});
});
Hinweis: Dies ist die bevorzugte Methode zum Definieren von Routen und Gruppen mit dem
$router-Objekt.
Gruppierung mit Middleware
Du kannst einer Gruppe von Routen auch Middleware zuweisen:
Flight::group('/api/v1', function () {
Flight::route('/users', function () {
// Matcht /api/v1/users
});
}, [ MyAuthMiddleware::class ]); // oder [ new MyAuthMiddleware() ], wenn du eine Instanz verwenden möchtest
Weitere Details findest du auf der Seite Gruppen-Middleware.
Ressourcen-Routing
Du kannst mit der resource-Methode eine Reihe von Routen für eine Ressource erstellen. Dadurch wird eine Reihe von Routen für eine Ressource erstellt, die den RESTful-Konventionen folgt.
Um eine Ressource zu erstellen, gehe wie folgt vor:
Flight::resource('/users', UsersController::class);
Im Hintergrund werden die folgenden Routen erstellt:
[
'index' => 'GET /users',
'create' => 'GET /users/create',
'store' => 'POST /users',
'show' => 'GET /users/@id',
'edit' => 'GET /users/@id/edit',
'update' => 'PUT /users/@id',
'destroy' => 'DELETE /users/@id'
]
Und dein Controller verwendet die folgenden Methoden:
class UsersController
{
public function index(): void
{
}
public function show(string $id): void
{
}
public function create(): void
{
}
public function store(): void
{
}
public function edit(string $id): void
{
}
public function update(string $id): void
{
}
public function destroy(string $id): void
{
}
}
Hinweis: Du kannst die neu hinzugefügten Routen mit
runwayanzeigen, indem duphp runway routesausführst.
Ressourcen-Routen anpassen
Es gibt einige Optionen, um die Ressourcen-Routen zu konfigurieren.
Alias-Basis
Du kannst die aliasBase konfigurieren. Standardmäßig ist der Alias der letzte Teil der angegebenen URL.
Zum Beispiel würde /users/ zu einer aliasBase von users führen. Wenn diese Routen erstellt werden, sind die Aliase users.index, users.create usw. Wenn du den Alias ändern möchtest, setze die aliasBase auf den gewünschten Wert.
Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Only und Except
Du kannst auch festlegen, welche Routen du erstellen möchtest, indem du die Optionen only und except verwendest.
// Whitelist nur diese Methoden und Blacklist den Rest
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// Blacklist nur diese Methoden und Whitelist den Rest
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);
Dies sind im Grunde Whitelist- und Blacklist-Optionen, mit denen du festlegen kannst, welche Routen du erstellen möchtest.
Middleware
Du kannst auch Middleware angeben, die für jede der durch die resource-Methode erstellten Routen ausgeführt werden soll.
Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);
Streaming-Antworten
Du kannst jetzt Antworten an den Client mit stream() oder streamWithHeaders() streamen.
Dies ist nützlich für das Senden großer Dateien, langlaufender Prozesse oder das Erzeugen großer Antworten.
Das Streaming einer Route wird etwas anders behandelt als eine normale Route.
Hinweis: Streaming-Antworten sind nur verfügbar, wenn
flight.v2.output_bufferingauffalsegesetzt ist.
Streamen mit manuellen Headern
Du kannst eine Antwort an den Client streamen, indem du die stream()-Methode auf einer Route verwendest. Wenn du dies tust, musst du alle Header von Hand setzen, bevor du irgendetwas an den Client ausgibst.
Dies geschieht mit der PHP-Funktion header() oder der Methode Flight::response()->setRealHeader().
Flight::route('/@filename', function($filename) {
$response = Flight::response();
// Offensichtlich würdest du den Pfad bereinigen und so weiter.
$fileNameSafe = basename($filename);
// Wenn du nach der Ausführung der Route zusätzliche Header setzen möchtest,
// musst du sie definieren, bevor irgendetwas ausgegeben wird.
// Sie müssen alle ein roher Aufruf der Funktion header() oder
// ein Aufruf von Flight::response()->setRealHeader() sein.
header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
// oder
$response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
$filePath = '/some/path/to/files/'.$fileNameSafe;
if (!is_readable($filePath)) {
Flight::halt(404, 'Datei nicht gefunden');
}
// setze die Content-Length manuell, wenn du möchtest
header('Content-Length: '.filesize($filePath));
// oder
$response->setRealHeader('Content-Length: '.filesize($filePath));
// Stream die Datei an den Client, während sie gelesen wird
readfile($filePath);
// Dies ist die magische Zeile hier
})->stream();
Streamen mit Headern
Du kannst auch die streamWithHeaders()-Methode verwenden, um die Header zu setzen, bevor du mit dem Streamen beginnst.
Flight::route('/stream-users', function() {
// Du kannst hier alle zusätzlichen Header hinzufügen, die du möchtest
// Du musst nur header() oder Flight::response()->setRealHeader() verwenden
// Wie auch immer du deine Daten abrufst, nur als Beispiel...
$users_stmt = Flight::db()->query("SELECT id, first_name, last_name FROM users");
echo '{';
$user_count = count($users);
while($user = $users_stmt->fetch(PDO::FETCH_ASSOC)) {
echo json_encode($user);
if(--$user_count > 0) {
echo ',';
}
// Dies ist erforderlich, um die Daten an den Client zu senden
ob_flush();
}
echo '}';
// So setzt du die Header, bevor du mit dem Streamen beginnst.
})->streamWithHeaders([
'Content-Type' => 'application/json',
'Content-Disposition' => 'attachment; filename="users.json"',
// optionaler Statuscode, Standard ist 200
'status' => 200
]);
Siehe auch
- Middleware - Verwendung von Middleware mit Routen für Authentifizierung, Protokollierung usw.
- Dependency Injection - Vereinfachung der Objekterstellung und -verwaltung in Routen.
- Warum ein Framework? - Verständnis der Vorteile eines Frameworks wie Flight.
- Erweiterung - Wie du Flight mit deiner eigenen Funktionalität erweiterst, einschließlich der
notFound-Methode. - php.net: preg_match - PHP-Funktion für den Abgleich regulärer Ausdrücke.
Fehlerbehebung
- Routenparameter werden nach Reihenfolge abgeglichen, nicht nach Namen. Stelle sicher, dass die Reihenfolge der Callback-Parameter der Routendefinition entspricht.
- Die Verwendung von
Flight::get()definiert keine Route; verwendeFlight::route('GET /...')für das Routing oder den Router-Objektkontext in Gruppen (z. B.$router->get(...)). - Die Eigenschaft
executedRoutewird nur gesetzt, nachdem eine Route ausgeführt wurde; sie ist vor der AusführungNULL. - Streaming erfordert, dass die Legacy-Output-Buffering-Funktion von Flight deaktiviert ist (
flight.v2.output_buffering = false). - Für Dependency Injection unterstützen nur bestimmte Routendefinitionen die containerbasierte Instanziierung.
404 Not Found oder unerwartetes Routenverhalten
Wenn du einen 404 Not Found-Fehler siehst (obwohl du dir sicher bist, dass die Route wirklich existiert und es kein Tippfehler ist), könnte das tatsächlich ein Problem damit sein, dass du in deinem Routen-Endpunkt einen Wert zurückgibst, anstatt ihn nur auszugeben. Der Grund dafür ist beabsichtigt, kann aber einige Entwickler überraschen.
Flight::route('/hello', function(){
// Dies könnte einen 404 Not Found-Fehler verursachen
return 'Hello World';
});
// Was du wahrscheinlich willst
Flight::route('/hello', function(){
echo 'Hello World';
});
Der Grund dafür ist ein spezieller Mechanismus im Router, der den Rückgabewert als Signal zum "Weitergehen zur nächsten Route" behandelt. Du kannst dieses Verhalten im Abschnitt Routing dokumentiert sehen.
Änderungsprotokoll
- v3: Ressourcen-Routing, Route-Aliase, Streaming-Unterstützung, Routengruppen und Middleware-Unterstützung hinzugefügt.
- v1: Überwiegender Teil der grundlegenden Funktionen verfügbar.
Learn/learn
Lerne Flight kennen
Flight ist ein schnelles, einfaches, erweiterbares Framework für PHP. Es ist sehr vielseitig und kann zum Erstellen jeder Art von Webanwendung verwendet werden. Es ist auf Einfachheit ausgelegt und so geschrieben, dass es leicht zu verstehen und zu verwenden ist – von Menschen und von KI-Programmierassistenten.
Hinweis: Du wirst Beispiele sehen, die
Flight::als statische Variable verwenden, und einige, die das$app->Engine-Objekt verwenden. Beide funktionieren austauschbar.$appund$this->appin einem Controller/Middleware sind der empfohlene Ansatz des Flight-Teams (und worauf das offizielle Skelett +AGENTS.mdfür neue Projekte standardisieren).
Kernkomponenten
Routing
Lerne, wie du Routen für deine Webanwendung verwaltest. Dies beinhaltet auch das Gruppieren von Routen, Routenparameter und Middleware.
Middleware
Lerne, wie du Middleware verwendest, um Anfragen und Antworten in deiner Anwendung zu filtern.
Autoloading
Lerne, wie du deine eigenen Klassen automatisch lädst. Die Groß-/Kleinschreibung der Ordner muss mit deinen Namespaces übereinstimmen – das Skelett verwendet App\ und PascalCase-Ordner wie app/Controller/.
Requests
Lerne, wie du Anfragen und Antworten in deiner Anwendung behandelst.
Responses
Lerne, wie du Antworten an deine Benutzer sendest.
HTML-Templates
Lerne, wie du HTML mit Twig (Skelett-Standard), Latte oder anderen Engines renderst – nicht nur mit den eingebauten PHP-Views.
Sicherheit
Lerne, wie du deine Anwendung vor häufigen Sicherheitsbedrohungen schützt.
Konfiguration
Lerne, wie du das Framework für deine Anwendung konfigurierst.
Event Manager
Lerne, wie du das Event-System verwendest, um deiner Anwendung benutzerdefinierte Events hinzuzufügen.
Flight erweitern
Lerne, wie du das Framework erweitern kannst, indem du deine eigenen Methoden und Klassen hinzufügst.
Methoden-Hooks und Filtern
Lerne, wie du Event-Hooks zu deinen Methoden und internen Framework-Methoden hinzufügst.
Dependency-Injection-Container (DIC)
Lerne, wie du Dependency-Injection-Container (DIC) verwendest, um die Abhängigkeiten deiner Anwendung zu verwalten.
Utility-Klassen
Collections
Collections werden verwendet, um Daten zu speichern und sie zur einfacheren Nutzung als Array oder als Objekt zugänglich zu machen.
JSON-Wrapper
Diese Klasse bietet ein paar einfache Funktionen, um das Kodieren und Dekodieren deines JSON konsistent zu gestalten.
SimplePdo
PDO kann manchmal mehr Kopfschmerzen bereiten als nötig. SimplePdo ist eine moderne PDO-Hilfsklasse mit praktischen Methoden wie insert(), update(), delete() und transaction(), die Datenbankoperationen viel einfacher machen.
PdoWrapper (Veraltet)
Der ursprüngliche PDO-Wrapper ist seit v3.18.0 veraltet. Bitte verwende stattdessen SimplePdo.
Uploaded Datei-Handler
Eine einfache Klasse, die hilft, hochgeladene Dateien zu verwalten und an einen dauerhaften Ort zu verschieben.
Wichtige Konzepte
Warum ein Framework?
Hier ist ein kurzer Artikel darüber, warum du ein Framework verwenden solltest. Es ist eine gute Idee, die Vorteile der Verwendung eines Frameworks zu verstehen, bevor du eines verwendest.
Zusätzlich wurde ein exzellentes Tutorial von @lubiana erstellt. Auch wenn es nicht im Detail auf Flight eingeht, hilft dir dieser Leitfaden, einige der wichtigsten Konzepte rund um ein Framework zu verstehen und warum sie vorteilhaft sind. Du findest das Tutorial hier.
Flight im Vergleich zu anderen Frameworks
Wenn du von einem anderen Framework wie Laravel, Slim, Fat-Free oder Symfony zu Flight migrierst, hilft dir diese Seite, die Unterschiede zwischen den beiden zu verstehen.
Weitere Themen
Unit Testing
Folge dieser Anleitung, um zu lernen, wie du deinen Flight-Code Unit-testest, damit er felsenfest ist.
KI & Entwicklererlebnis
Flight ist dafür gebaut, mit Code-LLMs zusammenzuarbeiten: AGENTS.md, Runway ai:*-Befehle und ein klares Skelett-Layout, damit Agenten dem Muster folgen.
Migration v2 -> v3
Die Abwärtskompatibilität wurde größtenteils beibehalten, aber es gibt einige Änderungen, die du beachten solltest, wenn du von v2 auf v3 migrierst.
Learn/unit_testing
Unit-Tests
Übersicht
Unit-Tests in Flight helfen Ihnen, sicherzustellen, dass Ihre Anwendung wie erwartet funktioniert, Fehler früh zu erkennen und Ihre Codebasis einfacher zu warten. Flight ist so konzipiert, dass es reibungslos mit PHPUnit funktioniert, dem beliebtesten PHP-Testframework.
Verständnis
Unit-Tests prüfen das Verhalten kleiner Teile Ihrer Anwendung (wie Controller oder Services) isoliert. In Flight bedeutet dies, zu testen, wie Ihre Routen, Controller und Logik auf verschiedene Eingaben reagieren – ohne sich auf globalen Zustand oder echte externe Dienste zu verlassen.
Wichtige Grundsätze:
- Verhalten testen, nicht Implementierung: Konzentrieren Sie sich darauf, was Ihr Code tut, nicht darauf, wie er es tut.
- Globalen Zustand vermeiden: Verwenden Sie Dependency Injection anstelle von
Flight::set()oderFlight::get(). - Externe Dienste mocken: Ersetzen Sie Dinge wie Datenbanken oder Mailer durch Test-Doubles.
- Tests schnell und fokussiert halten: Unit-Tests sollten keine echten Datenbanken oder APIs verwenden.
Grundlegende Verwendung
PHPUnit einrichten
- PHPUnit mit Composer installieren:
composer require --dev phpunit/phpunit - Erstellen Sie ein
tests-Verzeichnis im Stammverzeichnis Ihres Projekts. - Fügen Sie ein Test-Skript zu Ihrer
composer.jsonhinzu:"scripts": { "test": "phpunit --configuration phpunit.xml" } - Erstellen Sie eine
phpunit.xml-Datei:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Jetzt können Sie Ihre Tests mit composer test ausführen.
Testen eines einfachen Routen-Handlers
Angenommen, Sie haben eine Route, die eine E-Mail validiert:
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
return $this->app->json(['status' => 'success', 'message' => 'Valid email']);
}
}
Ein einfacher Test für diesen Controller:
use PHPUnit\Framework\TestCase;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$app->request()->data->email = 'invalid-email';
$controller = new UserController($app);
$controller->register();
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Tipps:
- Simulieren Sie POST-Daten mit
$app->request()->data. - Vermeiden Sie die Verwendung von statischen
Flight::-Aufrufen in Ihren Tests – verwenden Sie die$app-Instanz.
Verwenden von Dependency Injection für testbare Controller
Injizieren Sie Abhängigkeiten (wie Datenbank oder Mailer) in Ihre Controller, um sie in Tests einfach mocken zu können:
use flight\database\SimplePdo;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct($app, $db, $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $this->app->json(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
Und ein Test mit Mocks:
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$mockDb = $this->createMock(flight\database\SimplePdo::class);
$mockDb->method('runQuery')->willReturn(true);
$mockMailer = new class {
public $sentEmail = null;
public function sendWelcome($email) { $this->sentEmail = $email; return true; }
};
$app = new flight\Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserController($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
}
Erweiterte Verwendung
- Mocking: Verwenden Sie die integrierten Mocks von PHPUnit oder anonyme Klassen, um Abhängigkeiten zu ersetzen.
- Controller direkt testen: Instanziieren Sie Controller mit einer neuen
Engineund mocken Sie Abhängigkeiten. - Übermäßiges Mocken vermeiden: Lassen Sie echte Logik wo möglich laufen; mocken Sie nur externe Dienste.
Siehe auch
- Leitfaden für Unit-Tests – Ein umfassender Leitfaden zu Best Practices für Unit-Tests.
- Dependency-Injection-Container – So verwenden Sie DICs, um Abhängigkeiten zu verwalten und die Testbarkeit zu verbessern.
- Erweitern – So fügen Sie eigene Helfer hinzu oder überschreiben Kernklassen.
- SimplePdo – Vereinfacht Datenbankinteraktionen und ist in Tests einfacher zu mocken.
- Anfragen – Verarbeitung von HTTP-Anfragen in Flight.
- Antworten – Senden von Antworten an Benutzer.
- Unit-Tests und SOLID-Prinzipien – Erfahren Sie, wie SOLID-Prinzipien Ihre Unit-Tests verbessern können.
Fehlerbehebung
- Vermeiden Sie die Verwendung von globalem Zustand (
Flight::set(),$_SESSION, usw.) in Ihrem Code und Ihren Tests. - Wenn Ihre Tests langsam sind, schreiben Sie möglicherweise Integrationstests – mocken Sie externe Dienste, um Unit-Tests schnell zu halten.
- Wenn die Testeinrichtung komplex ist, ziehen Sie in Betracht, Ihren Code zu refaktorieren, um Dependency Injection zu verwenden.
Änderungsprotokoll
- v3.15.0 – Beispiele für Dependency Injection und Mocking hinzugefügt.
Learn/flight_vs_symfony
Flight vs Symfony
Was ist Symfony?
Symfony ist eine Reihe von wiederverwendbaren PHP-Komponenten und ein PHP-Framework für Webprojekte.
Das Standardfundament, auf dem die besten PHP-Anwendungen aufgebaut sind. Wählen Sie eine der 50 eigenständigen Komponenten für Ihre eigenen Anwendungen aus.
Beschleunigen Sie die Erstellung und Wartung Ihrer PHP-Webanwendungen. Beenden Sie wiederholende Codieraufgaben und genießen Sie die Kontrolle über Ihren Code.
Vor- und Nachteile im Vergleich zu Flight
Vorteile im Vergleich zu Flight
- Symfony hat ein riesiges Ökosystem von Entwicklern und Modulen, die zur Lösung von üblichen Problemen genutzt werden können.
- Symfony hat ein vollständiges ORM (Doctrine), das zur Interaktion mit Ihrer Datenbank verwendet werden kann.
- Symfony hat eine große Menge an Dokumentation und Tutorials, die zur Erkundung des Frameworks verwendet werden können.
- Symfony verfügt über Podcasts, Konferenzen, Treffen, Videos und weitere Ressourcen, die zur Erkundung des Frameworks genutzt werden können.
- Symfony richtet sich an erfahrene Entwickler, die eine umfangreiche Enterprise-Webanwendung erstellen möchten.
Nachteile im Vergleich zu Flight
- Symfony hat viel mehr im Hintergrund laufen als Flight. Dies geht zu einem dramatischen Kostenaufwand hinsichtlich der Leistung einher. Weitere Informationen finden Sie in den TechEmpower-Benchmarks.
- Flight richtet sich an Entwickler, die eine leichtgewichtige, schnelle und benutzerfreundliche Webanwendung erstellen möchten.
- Flight konzentriert sich auf Einfachheit und Benutzerfreundlichkeit.
- Eine der Kernfunktionen von Flight besteht darin, die Abwärtskompatibilität zu wahren.
- Flight hat keine Abhängigkeiten, während Symfony eine Vielzahl von Abhängigkeiten hat.
- Flight ist für Entwickler gedacht, die sich zum ersten Mal in die Welt der Frameworks begeben.
- Flight kann auch Unternehmensanwendungen erstellen, verfügt jedoch nicht über so viele Beispiele und Tutorials wie Symfony. Es erfordert auch mehr Disziplin seitens des Entwicklers, um die Dinge organisiert und gut strukturiert zu halten.
- Flight gibt dem Entwickler mehr Kontrolle über die Anwendung, während Symfony im Hintergrund einige Magie einfließen lassen kann.
Learn/flight_vs_another_framework
Vergleich von Flight mit einem anderen Framework
Wenn Sie von einem anderen Framework wie Laravel, Slim, Fat-Free oder Symfony zu Flight migrieren, hilft Ihnen diese Seite, die Unterschiede zwischen den beiden zu verstehen.
Laravel
Laravel ist ein funktionsreiches Framework mit allen Extras und einem erstaunlichen, auf Entwickler ausgerichteten Ökosystem, aber zu einem Preis in Leistung und Komplexität.
Sehen Sie den Vergleich zwischen Laravel und Flight.
Slim
Slim ist ein Micro-Framework, das Flight ähnelt. Es ist darauf ausgelegt, leichtgewichtig und einfach zu bedienen zu sein, kann aber etwas komplexer sein als Flight.
Sehen Sie den Vergleich zwischen Slim und Flight.
Fat-Free
Fat-Free ist ein Full-Stack-Framework in einem viel kleineren Paket. Obwohl es alle Werkzeuge im Werkzeugkasten hat, hat es eine Datenarchitektur, die einige Projekte komplexer machen kann, als sie sein müssen.
Sehen Sie den Vergleich zwischen Fat-Free und Flight.
Symfony
Symfony ist ein modulares Enterprise-Level-Framework, das darauf ausgelegt ist, flexibel und skalierbar zu sein. Für kleinere Projekte oder neuere Entwickler kann Symfony etwas überwältigend sein.
Sehen Sie den Vergleich zwischen Symfony und Flight.
Learn/pdo_wrapper
PdoWrapper PDO-Hilfsklasse
WARNUNG
Veraltet:
PdoWrapperist veraltet ab Flight v3.18.0. Es wird in einer zukünftigen Version nicht entfernt, aber nur für Abwärtskompatibilität gewartet. Bitte verwenden Sie stattdessen SimplePdo, das die gleiche Funktionalität plus zusätzliche Hilfsmethoden für gängige Datenbankoperationen bietet.
Überblick
Die PdoWrapper-Klasse in Flight ist ein freundlicher Helfer für die Arbeit mit Datenbanken unter Verwendung von PDO. Sie vereinfacht gängige Datenbankaufgaben, fügt einige nützliche Methoden zum Abrufen von Ergebnissen hinzu und gibt Ergebnisse als Collections zurück, um einen einfachen Zugriff zu ermöglichen. Sie unterstützt auch Query-Logging und Application Performance Monitoring (APM) für fortgeschrittene Anwendungsfälle.
Verständnis
Die Arbeit mit Datenbanken in PHP kann etwas umständlich sein, insbesondere bei der direkten Verwendung von PDO. PdoWrapper erweitert PDO und fügt Methoden hinzu, die Abfragen, Abrufen und Behandeln von Ergebnissen viel einfacher machen. Statt mit Prepared Statements und Fetch-Modi zu jonglieren, erhalten Sie einfache Methoden für gängige Aufgaben, und jede Zeile wird als Collection zurückgegeben, sodass Sie Array- oder Objekt-Notation verwenden können.
Sie können PdoWrapper als geteilten Service in Flight registrieren und es dann überall in Ihrer App über Flight::db() verwenden.
Grundlegende Verwendung
Registrieren des PDO-Helpers
Zuerst registrieren Sie die PdoWrapper-Klasse bei Flight:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
Nun können Sie Flight::db() überall verwenden, um Ihre Datenbankverbindung zu erhalten.
Ausführen von Abfragen
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
Verwenden Sie dies für INSERTs, UPDATEs oder wenn Sie Ergebnisse manuell abrufen möchten:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row is an array
}
Sie können es auch für Schreibvorgänge verwenden:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
Holen Sie einen einzelnen Wert aus der Datenbank:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): Collection
Holen Sie eine einzelne Zeile als Collection (Array/Objekt-Zugriff):
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// or
echo $user->name;
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
Holen Sie alle Zeilen als Array von Collections:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// or
echo $user->name;
}
Verwendung von IN()-Platzhaltern
Sie können ein einzelnes ? in einer IN()-Klausel verwenden und ein Array oder einen komma-separierten String übergeben:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
// or
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", ['1,2,3']);
Fortgeschrittene Verwendung
Query-Logging & APM
Wenn Sie die Query-Leistung verfolgen möchten, aktivieren Sie das APM-Tracking bei der Registrierung:
Flight::register('db', \flight\database\PdoWrapper::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [/* options */], true // letzter Parameter aktiviert APM
]);
Nach dem Ausführen von Abfragen können Sie sie manuell loggen, aber das APM loggt sie automatisch, wenn aktiviert:
Flight::db()->logQueries();
Dies löst ein Event (flight.db.queries) mit Verbindungs- und Query-Metriken aus, das Sie mit Flights Event-System abhören können.
Vollständiges Beispiel
Flight::route('/users', function () {
// Get all users
$users = Flight::db()->fetchAll('SELECT * FROM users');
// Stream all users
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// Get a single user
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// Get a single value
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// Special IN() syntax
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', ['1,2,3,4,5']);
// Insert a new user
Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
$insert_id = Flight::db()->lastInsertId();
// Update a user
Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);
// Delete a user
Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);
// Get the number of affected rows
$statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
$affected_rows = $statement->rowCount();
});
Siehe auch
- Collections - Lernen Sie, wie Sie die Collection-Klasse für einfachen Datenzugriff verwenden.
Fehlerbehebung
- Wenn Sie einen Fehler bezüglich der Datenbankverbindung erhalten, überprüfen Sie Ihre DSN, Benutzername, Passwort und Optionen.
- Alle Zeilen werden als Collections zurückgegeben – wenn Sie ein einfaches Array benötigen, verwenden Sie
$collection->getData(). - Für
IN (?)-Abfragen stellen Sie sicher, dass Sie ein Array oder einen komma-separierten String übergeben.
Änderungsprotokoll
- v3.2.0 - Erste Veröffentlichung von PdoWrapper mit grundlegenden Query- und Fetch-Methoden.
Learn/dependency_injection_container
Dependency-Injection-Container
Übersicht
Der Dependency-Injection-Container (DIC) ist eine leistungsstarke Erweiterung, mit der Sie die Abhängigkeiten Ihrer Anwendung verwalten können. Er ist auch einer der Hauptgründe, warum Flight gut mit KI-Programmierwerkzeugen und Unit-Tests funktioniert: Controller nehmen im Konstruktor, was sie benötigen, anstatt auf globale Variablen zuzugreifen.
Verständnis
Dependency Injection (DI) ist ein zentrales Konzept in modernen PHP-Frameworks und wird verwendet, um die Instanziierung und Konfiguration von Objekten zu verwalten. Einige Beispiele für DIC-Bibliotheken sind: flightphp/container, Dice, Pimple, PHP-DI und league/container.
Ein DIC ist eine ausgefallene Möglichkeit, Ihre Klassen an einem zentralen Ort zu erstellen und zu verwalten. Das ist nützlich, wenn Sie dasselbe Objekt an mehrere Klassen (Controller, Middleware, Befehle usw.) übergeben müssen.
Das offizielle flightphp/skeleton bindet Dice in app/config/services.php ein, ersetzt die gemeinsame flight\Engine-Instanz und löst Routenziele wie [App\Controller\HomeController::class, 'index'] auf. Bevorzugen Sie dieses Muster für neue Projekte, damit Menschen und Agenten dieselben Stellen bearbeiten.
Grundlegende Verwendung
Die alte Vorgehensweise könnte so aussehen:
require 'vendor/autoload.php';
// Klasse zur Verwaltung von Benutzern aus der Datenbank
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// in Ihrer routes.php-Datei
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// weitere UserController-Routen...
Flight::start();
Sie können am obigen Code sehen, dass wir ein neues PDO-Objekt erstellen und es an unsere UserController-Klasse übergeben. Das ist für eine kleine Anwendung in Ordnung, aber wenn Ihre Anwendung wächst, werden Sie feststellen, dass Sie dasselbe PDO-Objekt an mehreren Stellen erstellen oder weiterreichen. Hier kommt ein DIC ins Spiel.
Hier ist dasselbe Beispiel mit einem DIC (unter Verwendung von Dice):
require 'vendor/autoload.php';
// dieselbe Klasse wie oben. Nichts geändert
class UserController {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function view(int $id) {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
print_r($stmt->fetch());
}
}
// einen neuen Container erstellen
$container = new \Dice\Dice;
// eine Regel hinzufügen, die dem Container mitteilt, wie ein PDO-Objekt erstellt wird
// nicht vergessen, es wie unten gezeigt sich selbst zuzuweisen!
$container = $container->addRule('PDO', [
// shared bedeutet, dass jedes Mal dasselbe Objekt zurückgegeben wird
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// Dies registriert den Container-Handler, damit Flight weiß, dass er ihn verwenden soll.
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
// jetzt können wir den Container verwenden, um unseren UserController zu erstellen
Flight::route('/user/@id', [ UserController::class, 'view' ]);
Flight::start();
Ich wette, Sie denken vielleicht, dass dem Beispiel eine Menge zusätzlicher Code hinzugefügt wurde. Die Magie zeigt sich, wenn Sie einen weiteren Controller haben, der das PDO-Objekt benötigt.
// Wenn alle Ihre Controller einen Konstruktor haben, der ein PDO-Objekt benötigt,
// wird es automatisch in jede der folgenden Routen injiziert!!!
Flight::route('/company/@id', [ CompanyController::class, 'view' ]);
Flight::route('/organization/@id', [ OrganizationController::class, 'view' ]);
Flight::route('/category/@id', [ CategoryController::class, 'view' ]);
Flight::route('/settings', [ SettingsController::class, 'view' ]);
Ein zusätzlicher Vorteil der Nutzung eines DIC ist, dass Unit-Tests viel einfacher werden. Sie können ein Mock-Objekt erstellen und es an Ihre Klasse übergeben. Das ist ein großer Vorteil, wenn Sie Tests für Ihre Anwendung schreiben – und wenn ein KI-Assistent einen Controller generiert, gibt die Konstruktor-Injektion ihm ein klares, konsistentes Muster, dem er folgen kann (Unit-Testing-Leitfaden).
Einen zentralen DIC-Handler erstellen
Sie können einen zentralen DIC-Handler in Ihrer Services-Datei erstellen, indem Sie Ihre App erweitern. Hier ist ein Beispiel:
// services.php
// einen neuen Container erstellen
$container = new \Dice\Dice;
// nicht vergessen, es wie unten gezeigt sich selbst zuzuweisen!
$container = $container->addRule('PDO', [
// shared bedeutet, dass jedes Mal dasselbe Objekt zurückgegeben wird
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// jetzt können wir eine mappbare Methode erstellen, um jedes Objekt zu erstellen.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Dies registriert den Container-Handler, damit Flight weiß, dass er ihn für Controller/Middleware verwenden soll
Flight::registerContainerHandler(function($class, $params) {
return Flight::make($class, $params);
});
// nehmen wir an, wir haben die folgende Beispielklasse, die ein PDO-Objekt im Konstruktor erhält
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// Code, der eine E-Mail sendet
}
}
// Und schließlich können Sie Objekte mithilfe von Dependency Injection erstellen
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
flightphp/container
Flight hat ein Plugin, das einen einfachen PSR-11-konformen Container bereitstellt, den Sie für Ihre Dependency Injection verwenden können. Hier ist ein kurzes Beispiel zur Verwendung:
// index.php zum Beispiel
require 'vendor/autoload.php';
use flight\Container;
$container = new Container;
$container->set(PDO::class, fn(): PDO => new PDO('sqlite::memory:'));
Flight::registerContainerHandler([$container, 'get']);
class TestController {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function index() {
var_dump($this->pdo);
// wird dies korrekt ausgeben!
}
}
Flight::route('GET /', [TestController::class, 'index']);
Flight::start();
Erweiterte Verwendung von flightphp/container
Sie können Abhängigkeiten auch rekursiv auflösen. Hier ist ein Beispiel:
<?php
require 'vendor/autoload.php';
use flight\Container;
class User {}
interface UserRepository {
function find(int $id): ?User;
}
class PdoUserRepository implements UserRepository {
private PDO $pdo;
function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
function find(int $id): ?User {
// Implementierung ...
return null;
}
}
$container = new Container;
$container->set(PDO::class, static fn(): PDO => new PDO('sqlite::memory:'));
$container->set(UserRepository::class, PdoUserRepository::class);
$userRepository = $container->get(UserRepository::class);
var_dump($userRepository);
/*
object(PdoUserRepository)#4 (1) {
["pdo":"PdoUserRepository":private]=>
object(PDO)#3 (0) {
}
}
*/
DICE
Sie können auch Ihren eigenen DIC-Handler erstellen. Das ist nützlich, wenn Sie einen benutzerdefinierten Container verwenden möchten, der nicht PSR-11-konform ist (Dice). Siehe den Abschnitt grundlegende Verwendung für Details.
Zusätzlich gibt es einige hilfreiche Standardeinstellungen, die Ihnen die Arbeit mit Flight erleichtern.
Engine-Instanz (erforderlich für die $app-Injektion)
Wenn Sie flight\Engine in Controllern oder Middleware als Typ-Hinweis verwenden, darf Dice keine neue Engine erstellen. Ersetzen Sie sie durch dieselbe Instanz aus dem Bootstrap. Das macht das offizielle Skeleton, und es ist das Muster, das AGENTS.md für KI-generierte Controller erwartet:
// Irgendwo in Ihrem Bootstrap / services.php
use flight\Engine;
use flight\database\SimplePdo;
$app = Flight::app(); // oder $engine = Flight::app();
$container = new \Dice\Dice;
$container = $container->addRule('*', [
'substitutions' => [
// Kritisch: die gebootstrappte Engine wiederverwenden – Dice nicht `new Engine()` ausführen lassen
Engine::class => $app,
// SimplePdo für neuen Code bevorzugen
// SimplePdo::class => $db,
// Config::class => $config,
// \Twig\Environment::class => $twig,
]
]);
$app->registerContainerHandler(function ($class, $params) use ($container) {
return $container->create($class, $params);
});
// Optionaler Helfer für Nicht-Routen-Code
$app->map('make', function ($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// app/Controller/MyController.php (Skeleton-Layout – Ordner-Schreibweise entspricht dem Namespace)
namespace App\Controller;
use flight\Engine;
class MyController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
// Keine Flight::-Fassade in der App-Ebene – einfacher zu testen und klarer für KI-Tools
$this->app->render('welcome', ['message' => 'Hello']);
}
}
// app/config/routes.php
use App\Controller\MyController;
$router->get('/', [MyController::class, 'index']);
Wenn Sie die Engine-Substitution überspringen, könnte Dice eine zweite Engine erstellen und Ihr Controller wird keine Routen, Konfiguration oder das gemappte Twig-render aus dem Bootstrap teilen.
Weitere gemeinsame Services hinzufügen (SimplePdo, Config, Twig)
use flight\database\SimplePdo;
use flight\Engine;
// Nachdem Sie $db, $config, $twig in services.php erstellt haben:
$substitutions = [
Engine::class => $app,
SimplePdo::class => $db,
// App\Utils\Config::class => $config,
// \Twig\Environment::class => $twig,
];
$container = $container->addRule('*', [
'substitutions' => $substitutions,
]);
Dann können Controller SimplePdo $db (oder Ihren Konfigurationstyp) im Konstruktor entgegennehmen und müssen nie Flight::db() aufrufen. Das entspricht dem Unit-Testing-Leitfaden und dem Hausstil des Skeletons.
Weitere Klassen hinzufügen
Wenn Sie weitere Klassen zum Container hinzufügen möchten, ist das mit Dice einfach, da sie automatisch vom Container aufgelöst werden. Hier ist ein Beispiel:
$container = new \Dice\Dice;
// Wenn Sie keine Abhängigkeiten in Ihre Klassen injizieren müssen,
// müssen Sie nichts definieren!
Flight::registerContainerHandler(function($class, $params) use ($container) {
return $container->create($class, $params);
});
class MyCustomClass {
public function parseThing() {
return 'thing';
}
}
class UserController {
protected MyCustomClass $MyCustomClass;
public function __construct(MyCustomClass $MyCustomClass) {
$this->MyCustomClass = $MyCustomClass;
}
public function index() {
echo $this->MyCustomClass->parseThing();
}
}
Flight::route('/user', 'UserController->index');
PSR-11
Flight kann auch jeden PSR-11-konformen Container verwenden. Das bedeutet, dass Sie jeden Container verwenden können, der das PSR-11-Interface implementiert. Hier ist ein Beispiel mit dem PSR-11-Container von League:
require 'vendor/autoload.php';
use flight\database\SimplePdo;
// dieselbe UserController-Idee wie oben, mit SimplePdo als Typ-Hinweis statt rohem PDO
$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(SimplePdo::class);
$container->add(SimplePdo::class)
->addArgument('mysql:host=localhost;dbname=test')
->addArgument('user')
->addArgument('pass');
Flight::registerContainerHandler($container);
Flight::route('/user', [ 'UserController', 'view' ]);
Flight::start();
Das kann etwas ausführlicher sein als das vorherige Dice-Beispiel, erfüllt aber denselben Zweck mit denselben Vorteilen!
Siehe auch
- Installation – Skeleton-Layout und wo
services.phpliegt. - Autoloading –
App\-Namespaces und Ordner-Schreibweise. - Flight erweitern – Erfahren Sie, wie Sie Dependency Injection zu Ihren eigenen Klassen hinzufügen können, indem Sie das Framework erweitern.
- Konfiguration – Erfahren Sie, wie Sie Flight für Ihre Anwendung konfigurieren.
- Routing – Erfahren Sie, wie Sie Routen für Ihre Anwendung definieren und wie Dependency Injection mit Controllern funktioniert.
- Middleware – Erfahren Sie, wie Sie Middleware für Ihre Anwendung erstellen und wie Dependency Injection mit Middleware funktioniert.
- Unit-Testing – Warum Konstruktor-Injektion besser ist als
Flight::-Globale. - KI & Entwicklererfahrung – Ein DI-Muster für Menschen und Agenten.
- SimplePdo – Bevorzugter Datenbank-Helper für die Injektion.
Fehlerbehebung
- Wenn Sie Probleme mit Ihrem Container haben, stellen Sie sicher, dass Sie die korrekten Klassennamen an den Container übergeben.
- Controller, die
Engineals Typ-Hinweis verwenden, aber eine "leere" App erhalten: Fügen Sie die Engine-Substitution hinzu (siehe oben). Dice darf keine zweite Engine mitnewerstellen. - Klasse für
App\Controller\…nicht gefunden: Überprüfen Sie die Ordner-Schreibweise unterapp/Controller/– siehe Autoloading. - Der Handler muss das erstellte Objekt von
registerContainerHandlerzurückgeben (rufen SieFlight::make()nicht ohnereturnauf).
Änderungsprotokoll
- Dokumentation – Skeleton-Dice + Engine-Substitutionen, SimplePdo und
App\Controller-Layout für KI-freundliche Projekte dokumentiert. - v3.7.0 – Möglichkeit hinzugefügt, einen DIC-Handler bei Flight zu registrieren.
Learn/middleware
Middleware
Überblick
Flight unterstützt Route- und Gruppen-Route-Middleware. Middleware ist ein Teil Ihrer Anwendung, in dem Code ausgeführt wird, bevor (oder nach) dem Route-Callback. Dies ist eine großartige Möglichkeit, API-Authentifizierungsprüfungen in Ihrem Code hinzuzufügen oder zu überprüfen, ob der Benutzer die Berechtigung hat, auf die Route zuzugreifen.
Verständnis
Middleware kann Ihre App erheblich vereinfachen. Anstatt komplexer abstrakter Klassenvererbung oder Methoden-Überschreibungen ermöglicht Middleware Ihnen, Ihre Routen zu steuern, indem Sie Ihre benutzerdefinierte App-Logik zuweisen. Sie können Middleware wie ein Sandwich betrachten. Sie haben Brot außen und dann Schichten von Zutaten wie Salat, Tomaten, Fleisch und Käse. Stellen Sie sich vor, jede Anfrage ist wie ein Bissen des Sandwiches, bei dem Sie zuerst die äußeren Schichten essen und zum Kern vordringen.
Hier ist eine visuelle Darstellung, wie Middleware funktioniert. Dann zeigen wir Ihnen ein praktisches Beispiel, wie dies funktioniert.
Benutzeranfrage an URL /api ---->
Middleware->before() ausgeführt ----->
Callable/ Methode an /api ausgeführt und Antwort generiert ------>
Middleware->after() ausgeführt ----->
Benutzer erhält Antwort vom Server
Und hier ist ein praktisches Beispiel:
Benutzer navigiert zu URL /dashboard
LoggedInMiddleware->before() wird ausgeführt
before() prüft auf gültige angemeldete Sitzung
wenn ja, nichts tun und Ausführung fortsetzen
wenn nein, Benutzer zu /login umleiten
Callable/ Methode an /api ausgeführt und Antwort generiert
LoggedInMiddleware->after() hat nichts definiert, also lässt es die Ausführung fortfahren
Benutzer erhält Dashboard-HTML vom Server
Ausführungsreihenfolge
Middleware-Funktionen werden in der Reihenfolge ausgeführt, in der sie der Route hinzugefügt werden. Die Ausführung ähnelt der Art und Weise, wie Slim Framework dies handhabt.
before()-Methoden werden in der Reihenfolge ausgeführt, in der sie hinzugefügt wurden, und after()-Methoden werden in umgekehrter Reihenfolge ausgeführt.
Beispiel: Middleware1->before(), Middleware2->before(), Middleware2->after(), Middleware1->after().
Grundlegende Verwendung
Sie können Middleware als jede aufrufbare Methode verwenden, einschließlich einer anonymen Funktion oder einer Klasse (empfohlen).
Anonyme Funktion
Hier ist ein einfaches Beispiel:
Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
echo 'Middleware first!';
});
Flight::start();
// Dies wird "Middleware first! Here I am!" ausgeben
Hinweis: Bei der Verwendung einer anonymen Funktion wird nur eine
before()-Methode interpretiert. Sie können keinafter()-Verhalten mit einer anonymen Klasse definieren.
Verwendung von Klassen
Middleware kann (und sollte) als Klasse registriert werden. Wenn Sie die "after"-Funktionalität benötigen, müssen Sie eine Klasse verwenden.
class MyMiddleware {
public function before($params) {
echo 'Middleware first!';
}
public function after($params) {
echo 'Middleware last!';
}
}
$MyMiddleware = new MyMiddleware();
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware($MyMiddleware);
// auch ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);
Flight::start();
// Dies wird "Middleware first! Here I am! Middleware last!" anzeigen
Sie können auch nur den Klassenname der Middleware definieren, und sie wird die Klasse instanziieren.
Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware(MyMiddleware::class);
Hinweis: Wenn Sie nur den Namen der Middleware übergeben, wird sie automatisch vom Dependency Injection Container ausgeführt, und die Middleware wird mit den Parametern ausgeführt, die sie benötigt. Wenn kein Dependency Injection Container registriert ist, wird standardmäßig die
flight\Engine-Instanz in den__construct(Engine $app)übergeben.
Verwendung von Routen mit Parametern
Wenn Sie Parameter aus Ihrer Route benötigen, werden sie in einem einzelnen Array an Ihre Middleware-Funktion übergeben. (function($params) { ... } oder public function before($params) { ... }). Der Grund dafür ist, dass Sie Ihre Parameter in Gruppen strukturieren können und in einigen dieser Gruppen Ihre Parameter möglicherweise in einer anderen Reihenfolge erscheinen, was die Middleware-Funktion durch Verweis auf den falschen Parameter kaputt machen würde. Auf diese Weise können Sie sie nach Namen anstelle der Position zugreifen.
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
// jobId kann übergeben oder nicht übergeben werden
$jobId = $params['jobId'] ?? 0;
// vielleicht wenn es keine Job-ID gibt, müssen Sie nichts nachschlagen.
if($jobId === 0) {
return;
}
// Führen Sie eine Suche in Ihrer Datenbank durch
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
// Diese Gruppe unten erhält immer noch die Parent-Middleware
// Aber die Parameter werden in einem einzelnen Array
// in der Middleware übergeben.
$router->group('/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// mehr Routen...
});
}, [ RouteSecurityMiddleware::class ]);
Gruppierung von Routen mit Middleware
Sie können eine Route-Gruppe hinzufügen, und dann wird jede Route in dieser Gruppe dieselbe Middleware haben. Dies ist nützlich, wenn Sie eine Menge von Routen gruppieren müssen, z. B. mit einer Auth-Middleware, um den API-Schlüssel im Header zu prüfen.
// am Ende der group-Methode hinzugefügt
Flight::group('/api', function() {
// Diese "leere" Route passt tatsächlich zu /api
Flight::route('', function() { echo 'api'; }, false, 'api');
// Dies passt zu /api/users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// Dies passt zu /api/users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);
Wenn Sie eine globale Middleware auf alle Ihre Routen anwenden möchten, können Sie eine "leere" Gruppe hinzufügen:
// am Ende der group-Methode hinzugefügt
Flight::group('', function() {
// Dies ist immer noch /users
Flight::route('/users', function() { echo 'users'; }, false, 'users');
// Und dies ist immer noch /users/1234
Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ ApiAuthMiddleware::class ]); // oder [ new ApiAuthMiddleware() ], dasselbe
Häufige Anwendungsfälle
API-Schlüssel-Validierung
Wenn Sie Ihre /api-Routen schützen möchten, indem Sie überprüfen, ob der API-Schlüssel korrekt ist, können Sie das leicht mit Middleware handhaben.
use flight\Engine;
class ApiMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$authorizationHeader = $this->app->request()->getHeader('Authorization');
$apiKey = str_replace('Bearer ', '', $authorizationHeader);
// Führen Sie eine Suche in Ihrer Datenbank für den API-Schlüssel durch
$apiKeyHash = hash('sha256', $apiKey);
$hasValidApiKey = !!$this->db()->fetchField("SELECT 1 FROM api_keys WHERE hash = ? AND valid_date >= NOW()", [ $apiKeyHash ]);
if($hasValidApiKey !== true) {
$this->app->jsonHalt(['error' => 'Invalid API Key']);
}
}
}
// routes.php
$router->group('/api', function(Router $router) {
$router->get('/users', [ ApiController::class, 'getUsers' ]);
$router->get('/companies', [ ApiController::class, 'getCompanies' ]);
// mehr Routen...
}, [ ApiMiddleware::class ]);
Jetzt sind alle Ihre API-Routen durch diese API-Schlüssel-Validierungs-Middleware geschützt, die Sie eingerichtet haben! Wenn Sie mehr Routen in die Router-Gruppe einfügen, erhalten sie sofort denselben Schutz!
Anmeldungs-Validierung
Möchten Sie einige Routen schützen, damit sie nur für angemeldete Benutzer verfügbar sind? Das kann leicht mit Middleware erreicht werden!
use flight\Engine;
class LoggedInMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$session = $this->app->session();
if($session->get('logged_in') !== true) {
$this->app->redirect('/login');
exit;
}
}
}
// routes.php
$router->group('/admin', function(Router $router) {
$router->get('/dashboard', [ DashboardController::class, 'index' ]);
$router->get('/clients', [ ClientController::class, 'index' ]);
// mehr Routen...
}, [ LoggedInMiddleware::class ]);
Route-Parameter-Validierung
Möchten Sie Ihre Benutzer schützen, indem Sie verhindern, dass sie Werte in der URL ändern, um auf Daten zuzugreifen, die sie nicht sollten? Das kann mit Middleware gelöst werden!
use flight\Engine;
class RouteSecurityMiddleware {
protected Engine $app;
public function __construct(Engine $app) {
$this->app = $app;
}
public function before(array $params) {
$clientId = $params['clientId'];
$jobId = $params['jobId'];
// Führen Sie eine Suche in Ihrer Datenbank durch
$isValid = !!$this->app->db()->fetchField("SELECT 1 FROM client_jobs WHERE client_id = ? AND job_id = ?", [ $clientId, $jobId ]);
if($isValid !== true) {
$this->app->halt(400, 'You are blocked, muahahaha!');
}
}
}
// routes.php
$router->group('/client/@clientId/job/@jobId', function(Router $router) {
$router->get('', [ JobController::class, 'view' ]);
$router->put('', [ JobController::class, 'update' ]);
$router->delete('', [ JobController::class, 'delete' ]);
// mehr Routen...
}, [ RouteSecurityMiddleware::class ]);
Handhabung der Middleware-Ausführung
Sagen wir, Sie haben eine Auth-Middleware und möchten den Benutzer auf eine Login-Seite umleiten, wenn er nicht authentifiziert ist. Sie haben ein paar Optionen zur Verfügung:
- Sie können false von der Middleware-Funktion zurückgeben, und Flight gibt automatisch einen 403 Forbidden-Fehler zurück, aber ohne Anpassung.
- Sie können den Benutzer auf eine Login-Seite umleiten mit
Flight::redirect(). - Sie können einen benutzerdefinierten Fehler in der Middleware erstellen und die Ausführung der Route stoppen.
Einfach und Unkompliziert
Hier ist ein einfaches return false;-Beispiel:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
return false;
}
// da es wahr ist, läuft alles einfach weiter
}
}
Umleitungs-Beispiel
Hier ist ein Beispiel für die Umleitung des Benutzers auf eine Login-Seite:
class MyMiddleware {
public function before($params) {
$hasUserKey = Flight::session()->exists('user');
if ($hasUserKey === false) {
Flight::redirect('/login');
exit;
}
}
}
Benutzerdefiniertes Fehler-Beispiel
Sagen wir, Sie müssen einen JSON-Fehler werfen, weil Sie eine API bauen. Sie können das so tun:
class MyMiddleware {
public function before($params) {
$authorization = Flight::request()->getHeader('Authorization');
if(empty($authorization)) {
Flight::jsonHalt(['error' => 'You must be logged in to access this page.'], 403);
// oder
Flight::json(['error' => 'You must be logged in to access this page.'], 403);
exit;
// oder
Flight::halt(403, json_encode(['error' => 'You must be logged in to access this page.']));
}
}
}
Siehe auch
- Routing - Wie man Routen zu Controllern zuweist und Views rendert.
- Requests - Verständnis, wie man eingehende Anfragen handhabt.
- Responses - Wie man HTTP-Antworten anpasst.
- Dependency Injection - Vereinfachung der Objekterstellung und -verwaltung in Routen.
- Warum ein Framework? - Verständnis der Vorteile der Verwendung eines Frameworks wie Flight.
- Middleware-Ausführungsstrategie-Beispiel
Fehlerbehebung
- Wenn Sie eine Umleitung in Ihrer Middleware haben, aber Ihre App scheint nicht umzuleiten, stellen Sie sicher, dass Sie eine
exit;-Anweisung in Ihrer Middleware hinzufügen.
Änderungsprotokoll
- v3.1: Unterstützung für Middleware hinzugefügt.
Learn/filtering
Filtering
Überblick
Flight ermöglicht es Ihnen, gemappte Methoden vor und nach ihrem Aufruf zu filtern.
Verständnis
Es gibt keine vordefinierten Hooks, die Sie merken müssen. Sie können alle Standard-Framework-Methoden sowie alle benutzerdefinierten Methoden filtern, die Sie gemappt haben.
Eine Filterfunktion sieht so aus:
/**
* @param array $params Die an die gefilterte Methode übergebenen Parameter.
* @param string $output (nur v2 Output Buffering) Die Ausgabe der gefilterten Methode.
* @return bool Geben Sie true/void zurück oder geben Sie nichts zurück, um die Kette fortzusetzen, false, um die Kette zu unterbrechen.
*/
function (array &$params, string &$output): bool {
// Filtercode
}
Mit den übergebenen Variablen können Sie die Eingabeparameter und/oder die Ausgabe manipulieren.
Sie können einen Filter vor einer Methode ausführen, indem Sie Folgendes tun:
Flight::before('start', function (array &$params, string &$output): bool {
// Etwas tun
});
Sie können einen Filter nach einer Methode ausführen, indem Sie Folgendes tun:
Flight::after('start', function (array &$params, string &$output): bool {
// Etwas tun
});
Sie können so viele Filter wie gewünscht zu jeder Methode hinzufügen. Sie werden in der Reihenfolge aufgerufen, in der sie deklariert wurden.
Hier ist ein Beispiel für den Filterprozess:
// Eine benutzerdefinierte Methode mappen
Flight::map('hello', function (string $name) {
return "Hello, $name!";
});
// Einen Before-Filter hinzufügen
Flight::before('hello', function (array &$params, string &$output): bool {
// Den Parameter manipulieren
$params[0] = 'Fred';
return true;
});
// Einen After-Filter hinzufügen
Flight::after('hello', function (array &$params, string &$output): bool {
// Die Ausgabe manipulieren
$output .= " Have a nice day!";
return true;
});
// Die benutzerdefinierte Methode aufrufen
echo Flight::hello('Bob');
Dies sollte anzeigen:
Hello Fred! Have a nice day!
Wenn Sie mehrere Filter definiert haben, können Sie die Kette unterbrechen, indem Sie false in einer Ihrer Filterfunktionen zurückgeben:
Flight::before('start', function (array &$params, string &$output): bool {
echo 'one';
return true;
});
Flight::before('start', function (array &$params, string &$output): bool {
echo 'two';
// Dies beendet die Kette
return false;
});
// Dies wird nicht aufgerufen
Flight::before('start', function (array &$params, string &$output): bool {
echo 'three';
return true;
});
Hinweis: Kernmethoden wie
mapundregisterkönnen nicht gefiltert werden, da sie direkt aufgerufen und nicht dynamisch aufgerufen werden. Siehe Erweiterung von Flight für weitere Informationen.
Siehe auch
Fehlerbehebung
- Stellen Sie sicher, dass Sie
falseaus Ihren Filterfunktionen zurückgeben, wenn Sie möchten, dass die Kette stoppt. Wenn Sie nichts zurückgeben, wird die Kette fortgesetzt.
Changelog
- v2.0 - Erste Veröffentlichung.
Learn/requests
Requests
Übersicht
Flight kapselt die HTTP-Anfrage in ein einzelnes Objekt, das wie folgt zugänglich ist:
$request = Flight::request();
Verständnis
HTTP-Anfragen sind eines der Kernaspekte, die man über den HTTP-Lebenszyklus verstehen muss. Ein Benutzer führt eine Aktion in einem Webbrowser oder einem HTTP-Client aus, und sie senden eine Reihe von Headern, Body, URL usw. an Ihr Projekt. Sie können diese Header (die Sprache des Browsers, welche Art von Kompression sie handhaben können, den User-Agent usw.) erfassen und den Body sowie die URL, die an Ihre Flight-Anwendung gesendet wird, erfassen. Diese Anfragen sind essenziell, damit Ihre App versteht, was als Nächstes zu tun ist.
Grundlegende Verwendung
PHP hat mehrere Super-Globalen, einschließlich $_GET, $_POST, $_REQUEST, $_SERVER, $_FILES und $_COOKIE. Flight abstrahiert diese in handliche Collections. Sie können die Eigenschaften query, data, cookies und files als Arrays oder Objekte zugreifen.
Hinweis: Es wird STRONGLICH davon abgeraten, diese Super-Globalen in Ihrem Projekt zu verwenden, und sie sollten über das
request()-Objekt referenziert werden.
Hinweis: Es gibt keine Abstraktion für
$_ENVverfügbar.
$_GET
Sie können das $_GET-Array über die Eigenschaft query zugreifen:
// GET /search?keyword=something
Flight::route('/search', function(){
$keyword = Flight::request()->query['keyword'];
// oder
$keyword = Flight::request()->query->keyword;
echo "You are searching for: $keyword";
// query a database or something else with the $keyword
});
$_POST
Sie können das $_POST-Array über die Eigenschaft data zugreifen:
Flight::route('POST /submit', function(){
$name = Flight::request()->data['name'];
$email = Flight::request()->data['email'];
// oder
$name = Flight::request()->data->name;
$email = Flight::request()->data->email;
echo "You submitted: $name, $email";
// save to a database or something else with the $name and $email
});
$_COOKIE
Sie können das $_COOKIE-Array über die Eigenschaft cookies zugreifen:
Flight::route('GET /login', function(){
$savedLogin = Flight::request()->cookies['myLoginCookie'];
// oder
$savedLogin = Flight::request()->cookies->myLoginCookie;
// check if it's really saved or not and if it is auto log them in
if($savedLogin) {
Flight::redirect('/dashboard');
return;
}
});
Für Hilfe beim Setzen neuer Cookie-Werte siehe overclokk/cookie
$_SERVER
Es gibt einen Shortcut, um das $_SERVER-Array über die Methode getVar() zugreifen:
$host = Flight::request()->getVar('HTTP_HOST');
$_FILES
Sie können hochgeladene Dateien über die Eigenschaft files zugreifen:
// raw access to $_FILES property. See below for recommended approach
$uploadedFile = Flight::request()->files['myFile'];
// oder
$uploadedFile = Flight::request()->files->myFile;
Siehe Uploaded File Handler für mehr Infos.
Verarbeiten von Datei-Uploads
v3.12.0
Sie können Datei-Uploads mit dem Framework mithilfe einiger Hilfsmethoden verarbeiten. Es kommt im Wesentlichen darauf an, die Dateidaten aus der Anfrage zu ziehen und sie an einen neuen Ort zu verschieben.
Flight::route('POST /upload', function(){
// If you had an input field like <input type="file" name="myFile">
$uploadedFileData = Flight::request()->getUploadedFiles();
$uploadedFile = $uploadedFileData['myFile'];
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});
Wenn Sie mehrere Dateien hochgeladen haben, können Sie durch sie iterieren:
Flight::route('POST /upload', function(){
// If you had an input field like <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
foreach ($uploadedFiles as $uploadedFile) {
$uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
}
});
Sicherheitshinweis: Validieren und sanitieren Sie immer Benutzereingaben, insbesondere bei Datei-Uploads. Validieren Sie immer den Typ der Erweiterungen, die Sie zum Hochladen erlauben, aber Sie sollten auch die "Magic Bytes" der Datei validieren, um sicherzustellen, dass es tatsächlich der Typ der Datei ist, den der Benutzer angibt. Es gibt Artikel und Bibliotheken, die dabei helfen.
Request Body
Um den rohen HTTP-Request-Body zu erhalten, z. B. bei POST/PUT-Anfragen, können Sie Folgendes tun:
Flight::route('POST /users/xml', function(){
$xmlBody = Flight::request()->getBody();
// do something with the XML that was sent.
});
JSON Body
Wenn Sie eine Anfrage mit dem Content-Type application/json und den Beispieldaten {"id": 123} erhalten, ist sie über die Eigenschaft data verfügbar:
$id = Flight::request()->data->id;
Request Headers
Sie können Request-Header mit der Methode getHeader() oder getHeaders() zugreifen:
// Maybe you need Authorization header
$host = Flight::request()->getHeader('Authorization');
// oder
$host = Flight::request()->header('Authorization');
// If you need to grab all headers
$headers = Flight::request()->getHeaders();
// oder
$headers = Flight::request()->headers();
Request Method
Sie können die Request-Methode über die Eigenschaft method oder die Methode getMethod() zugreifen:
$method = Flight::request()->method; // actually populated by getMethod()
$method = Flight::request()->getMethod();
Hinweis: Die Methode getMethod() zieht zunächst die Methode aus $_SERVER['REQUEST_METHOD'], dann kann sie durch $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] überschrieben werden, falls vorhanden, oder $_REQUEST['_method'], falls vorhanden.
Eigenschaften des Request-Objekts
Das Request-Objekt stellt die folgenden Eigenschaften bereit:
- body - Der rohe HTTP-Request-Body
- url - Die angeforderte URL
- base - Das übergeordnete Unterverzeichnis der URL
- method - Die Request-Methode (GET, POST, PUT, DELETE)
- referrer - Die Referrer-URL
- ip - IP-Adresse des Clients
- ajax - Ob es sich um eine AJAX-Anfrage handelt
- scheme - Das Server-Protokoll (http, https)
- user_agent - Browser-Informationen
- type - Der Content-Type
- length - Die Content-Länge
- query - Query-String-Parameter
- data - Post-Daten oder JSON-Daten
- cookies - Cookie-Daten
- files - Hochgeladene Dateien
- secure - Ob die Verbindung sicher ist
- accept - HTTP-Accept-Parameter
- proxy_ip - Proxy-IP-Adresse des Clients. Scannt das
$_SERVER-Array nachHTTP_CLIENT_IP,HTTP_X_FORWARDED_FOR,HTTP_X_FORWARDED,HTTP_X_CLUSTER_CLIENT_IP,HTTP_FORWARDED_FOR,HTTP_FORWARDEDin dieser Reihenfolge. - host - Der Request-Hostname
- servername - Der SERVER_NAME aus
$_SERVER
Hilfsmethoden
Es gibt ein paar Hilfsmethoden, um Teile einer URL zusammenzusetzen oder mit bestimmten Headern umzugehen.
Volle URL
Sie können die volle Request-URL mit der Methode getFullUrl() zugreifen:
$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar
Basis-URL
Sie können die Basis-URL mit der Methode getBaseUrl() zugreifen:
// http://example.com/path/to/something/cool?query=yes+thanks
$url = Flight::request()->getBaseUrl();
// https://example.com
// Notice, no trailing slash.
Query-Parsing
Sie können eine URL an die Methode parseQuery() übergeben, um den Query-String in ein assoziatives Array zu parsen:
$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']
Verhandeln von Content-Accept-Types
v3.17.2
Sie können die Methode negotiateContentType() verwenden, um den besten Content-Type für die Antwort basierend auf dem vom Client gesendeten Accept-Header zu bestimmen.
// Example Accept header: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
// The below defines what you support.
$availableTypes = ['application/json', 'application/xml'];
$typeToServe = Flight::request()->negotiateContentType($availableTypes);
if ($typeToServe === 'application/json') {
// Serve JSON response
} elseif ($typeToServe === 'application/xml') {
// Serve XML response
} else {
// Default to something else or throw an error
}
Hinweis: Wenn keiner der verfügbaren Typen im
Accept-Header gefunden wird, gibt die Methodenullzurück. Wenn keinAccept-Header definiert ist, gibt die Methode den ersten Typ im$availableTypes-Array zurück.
Siehe auch
- Routing - So ordnen Sie Routen Controllern zu und rendern Views.
- Responses - So passen Sie HTTP-Antworten an.
- Warum ein Framework? - Wie Anfragen in das große Ganze passen.
- Collections - Arbeiten mit Datensammlungen.
- Uploaded File Handler - Behandeln von Datei-Uploads.
Fehlerbehebung
request()->ipundrequest()->proxy_ipkönnen unterschiedlich sein, wenn Ihr Webserver hinter einem Proxy, Load Balancer usw. steht.
Changelog
- v3.17.2 - Added negotiateContentType()
- v3.12.0 - Added ability to handle file uploads through the request object.
- v1.0 - Initial release.
Learn/why_frameworks
Warum ein Framework?
Einige Programmierer sind vehement gegen die Verwendung von Frameworks. Sie argumentieren, dass Frameworks aufgebläht, langsam und schwer zu erlernen sind. Sie sagen, dass Frameworks unnötig sind und dass man besseren Code ohne sie schreiben kann. Es gibt sicherlich einige überzeugende Argumente gegen die Verwendung von Frameworks vorzubringen. Allerdings gibt es auch viele Vorteile bei der Verwendung von Frameworks.
Gründe für die Verwendung eines Frameworks
Hier sind ein paar Gründe, warum Sie in Betracht ziehen sollten, ein Framework zu verwenden:
- Schnelle Entwicklung: Frameworks bieten von Haus aus viel Funktionalität. Das bedeutet, dass Sie Webanwendungen schneller erstellen können. Sie müssen nicht so viel Code schreiben, da das Framework einen Großteil der Funktionalität bereitstellt, die Sie benötigen.
- Konsistenz: Frameworks bieten eine konsistente Möglichkeit der Durchführung von Aufgaben. Dies erleichtert es Ihnen zu verstehen, wie der Code funktioniert, und anderen Entwicklern, Ihren Code zu verstehen. Wenn Sie es Script für Script haben, könnte die Konsistenz zwischen den Skripts verloren gehen, besonders wenn Sie mit einem Team von Entwicklern arbeiten.
- Sicherheit: Frameworks bieten Sicherheitsfunktionen, die Ihre Webanwendungen vor gängigen Sicherheitsbedrohungen schützen. Das bedeutet, dass Sie sich nicht so sehr um die Sicherheit kümmern müssen, da das Framework einen Großteil davon für Sie erledigt.
- Gemeinschaft: Frameworks haben große Entwickler-Communities, die zum Framework beitragen. Das bedeutet, dass Sie Hilfe von anderen Entwicklern erhalten können, wenn Sie Fragen oder Probleme haben. Es bedeutet auch, dass es viele Ressourcen gibt, die Ihnen helfen können, zu lernen, wie das Framework verwendet wird.
- Beste Praktiken: Frameworks werden unter Verwendung von besten Praktiken erstellt. Das bedeutet, dass Sie vom Framework lernen und die gleichen besten Praktiken in Ihrem eigenen Code verwenden können. Dies kann Ihnen helfen, ein besserer Programmierer zu werden. Manchmal weiß man nicht, was man nicht weiß, und das kann einem am Ende schaden.
- Erweiterbarkeit: Frameworks sind darauf ausgelegt, erweitert zu werden. Das bedeutet, dass Sie Ihre eigene Funktionalität zum Framework hinzufügen können. Dies ermöglicht es Ihnen, Webanwendungen zu erstellen, die auf Ihre spezifischen Bedürfnisse zugeschnitten sind.
Flight ist ein Mikro-Framework. Das bedeutet, dass es klein und leichtgewichtig ist. Es bietet nicht so viele Funktionen wie größere Frameworks wie Laravel oder Symfony. Allerdings bietet es viele der Funktionen, die Sie benötigen, um Webanwendungen zu erstellen. Es ist auch einfach zu erlernen und zu verwenden. Das macht es zu einer guten Wahl, um Webanwendungen schnell und einfach zu erstellen. Wenn Sie neu in der Welt der Frameworks sind, ist Flight ein großartiges Anfänger-Framework, mit dem Sie beginnen können. Es hilft Ihnen, die Vorteile der Verwendung von Frameworks kennenzulernen, ohne Sie mit zu viel Komplexität zu überfordern. Nachdem Sie etwas Erfahrung mit Flight gesammelt haben, wird es einfacher sein, auf komplexere Frameworks wie Laravel oder Symfony umzusteigen, Flight kann jedoch immer noch eine erfolgreiche robuste Anwendung ermöglichen.
Was ist Routing?
Routing ist der Kern des Flight Frameworks, aber was genau ist das? Routing ist der Prozess, bei dem eine URL genommen und mit einer bestimmten Funktion in Ihrem Code abgeglichen wird.
So können Sie Ihre Website basierend auf der angeforderten URL unterschiedliche Dinge tun lassen. Zum Beispiel möchten Sie möglicherweise das Profil eines Benutzers anzeigen, wenn er
/user/1234 besucht, aber eine Liste aller Benutzer anzeigen, wenn er /users besucht. All dies geschieht durch Routing.
Es könnte etwa so funktionieren:
- Ein Benutzer geht in Ihren Browser und gibt
http://beispiel.com/user/1234ein. - Der Server empfängt die Anfrage, betrachtet die URL und leitet sie an Ihren Flight-Anwendungscode weiter.
- Angenommen, in Ihrem Flight-Code haben Sie so etwas wie
Flight::route('/user/@id', [ 'BenutzerController', 'BenutzerprofilAnzeigen' ]);. Ihr Flight-Anwendungscode betrachtet die URL und erkennt, dass sie mit einem von Ihnen definierten Routen übereinstimmt, und führt dann den für diese Route definierten Code aus. - Der Flight-Router wird dann den
BenutzerprofilAnzeigen($id)-Methode in derBenutzerController-Klasse aufrufen und die1234als$id-Argument an die Methode übergeben. - Der Code in Ihrer
BenutzerprofilAnzeigen()-Methode wird dann ausgeführt und das tun, was Sie ihm gesagt haben. Sie können z. B. HTML für die Profilseite des Benutzers ausgeben oder wenn es sich um eine RESTful API handelt, können Sie eine JSON-Antwort mit den Benutzerinformationen ausgeben. - Flight fasst das Ganze in eine hübsche Schleife, generiert die Antwortheader und sendet sie an den Browser des Benutzers zurück.
- Der Benutzer ist erfüllt und verteilt sich selbst eine herzliche Umarmung!
Und warum ist das wichtig?
Eine ordnungsgemäß zentralisierte Routerung kann tatsächlich Ihr Leben dramatisch vereinfachen! Es kann nur anfangs schwer zu erkennen sein. Hier sind ein paar Gründe:
- Zentralisierte Routerung: Sie können alle Ihre Routen an einem Ort aufbewahren. Dies erleichtert es Ihnen zu sehen, welche Routen Sie haben und was sie tun. Es erleichtert auch das Ändern, wenn Sie müssen.
- Routenparameter: Sie können Routenparameter verwenden, um Daten an Ihre Routenmethoden zu übergeben. Dies ist eine großartige Möglichkeit, Ihren Code sauber und organisiert zu halten.
- Routengruppen: Sie können Routen zusammenfassen. Dies ist großartig, um Ihren Code organisiert zu halten und Middleware auf eine Gruppe von Routen anzuwenden.
- Routenaliasing: Sie können einer Route einen Alias zuweisen, damit die URL später in Ihrem Code dynamisch generiert werden kann (z. B. wie eine Vorlage). Zum Beispiel, anstatt
/user/1234fest im Code zu codieren, könnten Sie stattdessen auf den Aliasuser_viewverweisen und dieidals Parameter übergeben. Das ist großartig, falls Sie sich später dazu entscheiden, sie auf/admin/user/1234zu ändern. Sie müssen nicht alle fest codierten URLs ändern, sondern nur die URL, die der Route zugeordnet ist. - Routen-Middleware: Sie können Middleware zu Ihren Routen hinzufügen. Middleware ist unglaublich stark darin, spezifische Verhaltensweisen zu Ihrer Anwendung hinzuzufügen, wie z. B. die Authentifizierung, dass ein bestimmter Benutzer auf eine Route oder eine Gruppe von Routen zugreifen kann.
Sie sind sicherlich vertraut mit dem Script für Script-Weg, eine Website zu erstellen. Sie könnten eine Datei namens index.php haben, die eine Reihe von if-Anweisungen enthält, um die URL zu überprüfen und dann eine bestimmte Funktion auf der Grundlage der URL auszuführen. Dies ist eine Form der Routenführung, aber sie ist nicht sehr organisiert und kann schnell außer Kontrolle geraten. Flights Routensystem ist eine viel organisiertere und leistungsfähigere Art, die Routenführung zu handhaben.
Dies hier?
// /user/view_profile.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
viewUserProfile($id);
}
// /user/edit_profile.php?id=1234
if ($_GET['id']) {
$id = $_GET['id'];
editUserProfile($id);
}
// etc...
Oder das hier?
// index.php
Flight::route('/user/@id', [ 'BenutzerController', 'BenutzerprofilAnzeigen' ]);
Flight::route('/user/@id/edit', [ 'BenutzerController', 'BenutzerprofilBearbeiten' ]);
// Vielleicht in Ihrem app/controllers/UserController.php
class UserController {
public function viewUserProfile($id) {
// Mach etwas
}
public function editUserProfile($id) {
// Mach etwas
}
}
Hoffentlich erkennen Sie langsam die Vorteile der Verwendung eines zentralisierten Routingsystems. Es ist viel einfacher zu verwalten und zu verstehen auf lange Sicht!
Anfragen und Antworten
Flight bietet eine einfache und unkomplizierte Möglichkeit, Anfragen und Antworten zu bearbeiten. Dies ist der Kern dessen, was ein Web-Framework macht. Es nimmt eine Anfrage von einem Benutzerbrowser entgegen, verarbeitet sie und sendet dann eine Antwort zurück. So können Sie Webanwendungen erstellen, die Dinge wie das Anzeigen eines Benutzerprofils, das Einloggen eines Benutzers oder das Posten eines neuen Blogposts ermöglichen.
Anfragen
Eine Anfrage ist das, was der Browser eines Benutzers an Ihren Server sendet, wenn er Ihre Website besucht. Diese Anfrage enthält Informationen darüber, was der Benutzer tun möchte. Zum Beispiel könnte sie Informationen darüber enthalten, welche URL der Benutzer besuchen möchte, welche Daten der Benutzer an Ihren Server senden möchte oder welche Art von Daten der Benutzer von Ihrem Server erhalten möchte. Es ist wichtig zu wissen, dass eine Anfrage schreibgeschützt ist. Sie können die Anfrage nicht ändern, aber Sie können daraus lesen.
Flight bietet eine einfache Möglichkeit, Informationen zur Anfrage abzurufen. Sie können über die Methode Flight::request() Informationen zur Anfrage abrufen. Diese Methode gibt ein Request-Objekt zurück, das Informationen zur Anfrage enthält. Mit diesem Objekt können Sie Informationen zur Anfrage abrufen, wie die URL, die Methode oder die Daten, die der Benutzer an Ihren Server gesendet hat.
Antworten
Eine Antwort ist das, was Ihr Server an den Browser eines Benutzers zurücksendet, wenn er Ihre Website besucht. Diese Antwort enthält Informationen darüber, was Ihr Server tun möchte. Zum Beispiel könnte es Informationen darüber enthalten, welche Art von Daten Ihr Server an den Benutzer senden möchte, welche Art von Daten Ihr Server von dem Benutzer erhalten möchte oder welche Art von Daten Ihr Server auf dem Computer des Benutzers speichern möchte.
Flight bietet eine einfache Möglichkeit, eine Antwort an den Browser eines Benutzers zu senden. Sie können eine Antwort mit der Methode Flight::response() senden. Diese Methode nimmt ein Response-Objekt als Argument und sendet die Antwort an den Browser des Benutzers.
Sie können dieses Objekt verwenden, um eine Antwort an den Browser des Benutzers zu senden, wie z. B. HTML, JSON oder eine Datei. Flight hilft Ihnen dabei, einige Teile der Antwort automatisch zu generieren, um die Dinge zu vereinfachen, aber letztendlich haben Sie die Kontrolle darüber, was Sie dem Benutzer zurücksenden.
Learn/responses
Responses
Überblick
Flight hilft dabei, Teile der Response-Header für Sie zu generieren, aber Sie haben die meiste Kontrolle darüber, was Sie an den Benutzer zurücksenden. Meistens greifen Sie direkt auf das response()-Objekt zu, aber Flight bietet einige Hilfsmethoden, um einige der Response-Header für Sie zu setzen.
Verständnis
Nachdem der Benutzer seine request-Anfrage an Ihre Anwendung gesendet hat, müssen Sie eine angemessene Response für sie generieren. Sie haben Ihnen Informationen wie die bevorzugte Sprache, ob sie bestimmte Kompressionstypen handhaben können, ihren User Agent usw. gesendet, und nach der Verarbeitung von allem ist es Zeit, ihnen eine angemessene Response zurückzusenden. Dies kann das Setzen von Headern, das Ausgeben eines HTML- oder JSON-Bodys für sie oder das Weiterleiten zu einer Seite sein.
Grundlegende Verwendung
Senden eines Response-Bodys
Flight verwendet ob_start(), um die Ausgabe zu puffern. Das bedeutet, Sie können echo oder print verwenden, um eine Response an den Benutzer zu senden, und Flight wird sie erfassen und mit den entsprechenden Headern an den Benutzer zurücksenden.
// Dies sendet "Hello, World!" an den Browser des Benutzers
Flight::route('/', function() {
echo "Hello, World!";
});
// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!
Als Alternative können Sie die write()-Methode aufrufen, um zum Body hinzuzufügen.
// Dies sendet "Hello, World!" an den Browser des Benutzers
Flight::route('/', function() {
// ausführlich, aber erledigt den Job manchmal, wenn Sie es brauchen
Flight::response()->write("Hello, World!");
// wenn Sie den Body abrufen möchten, den Sie zu diesem Zeitpunkt gesetzt haben
// können Sie das so tun
$body = Flight::response()->getBody();
});
JSON
Flight bietet Unterstützung für das Senden von JSON- und JSONP-Responses. Um eine JSON-Response zu senden, geben Sie einige Daten weiter, die JSON-kodiert werden sollen:
Flight::route('/@companyId/users', function(int $companyId) {
// holen Sie irgendwie Ihre Benutzer aus einer Datenbank, z.B.
$users = Flight::db()->fetchAll("SELECT id, first_name, last_name FROM users WHERE company_id = ?", [ $companyId ]);
Flight::json($users);
});
// [{"id":1,"first_name":"Bob","last_name":"Jones"}, /* mehr Benutzer */ ]
Hinweis: Standardmäßig sendet Flight einen
Content-Type: application/json-Header mit der Response. Es verwendet auch die FlagsJSON_THROW_ON_ERRORundJSON_UNESCAPED_SLASHESbeim Kodieren des JSON.
JSON mit Statuscode
Sie können auch einen Statuscode als zweiten Argument übergeben:
Flight::json(['id' => 123], 201);
JSON mit Pretty Print
Sie können auch ein Argument an die letzte Position übergeben, um Pretty Printing zu aktivieren:
Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);
Ändern der JSON-Argument-Reihenfolge
Flight::json() ist eine sehr veraltete Methode, aber das Ziel von Flight ist es, die Abwärtskompatibilität für Projekte aufrechtzuerhalten. Es ist eigentlich sehr einfach, wenn Sie die Reihenfolge der Argumente neu gestalten möchten, um eine einfachere Syntax zu verwenden, können Sie die JSON-Methode einfach neu zuordnen wie jede andere Flight-Methode:
Flight::map('json', function($data, $code = 200, $options = 0) {
// jetzt müssen Sie nicht mehr `true, 'utf-8'` verwenden, wenn Sie die json()-Methode nutzen!
Flight::_json($data, $code, true, 'utf-8', $options);
}
// Und jetzt kann sie so verwendet werden
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);
JSON und Stoppen der Ausführung
v3.10.0
Wenn Sie eine JSON-Response senden und die Ausführung stoppen möchten, können Sie die jsonHalt()-Methode verwenden. Dies ist nützlich für Fälle, in denen Sie auf eine Art von Autorisierung prüfen und wenn der Benutzer nicht autorisiert ist, können Sie sofort eine JSON-Response senden, den bestehenden Body-Inhalt löschen und die Ausführung stoppen.
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Prüfen, ob der Benutzer autorisiert ist
if($authorized === false) {
Flight::jsonHalt(['error' => 'Unauthorized'], 401);
// kein exit; hier benötigt.
}
// Mit dem Rest der Route fortfahren
});
Vor v3.10.0 hätten Sie etwas wie das tun müssen:
Flight::route('/users', function() {
$authorized = someAuthorizationCheck();
// Prüfen, ob der Benutzer autorisiert ist
if($authorized === false) {
Flight::halt(401, json_encode(['error' => 'Unauthorized']));
}
// Mit dem Rest der Route fortfahren
});
Löschen eines Response-Bodys
Wenn Sie den Response-Body löschen möchten, können Sie die clearBody-Methode verwenden:
Flight::route('/', function() {
if($someCondition) {
Flight::response()->write("Hello, World!");
} else {
Flight::response()->clearBody();
}
});
Der obige Anwendungsfall ist wahrscheinlich nicht üblich, könnte aber häufiger vorkommen, wenn dies in einem Middleware verwendet wird.
Ausführen eines Callbacks auf dem Response-Body
Sie können einen Callback auf dem Response-Body ausführen, indem Sie die addResponseBodyCallback-Methode verwenden:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
});
// Dies wird alle Responses für jede Route gzippen
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
Sie können mehrere Callbacks hinzufügen, und sie werden in der Reihenfolge ausgeführt, in der sie hinzugefügt wurden. Da dies jede callable akzeptieren kann, kann es ein Klassen-Array [ $class, 'method' ], eine Closure $strReplace = function($body) { str_replace('hi', 'there', $body); }; oder einen Funktionsnamen 'minify' akzeptieren, wenn Sie z.B. eine Funktion haben, um Ihren HTML-Code zu minimieren.
Hinweis: Route-Callbacks funktionieren nicht, wenn Sie die Konfigurationsoption flight.v2.output_buffering verwenden.
Spezifischer Route-Callback
Wenn Sie möchten, dass dies nur auf eine spezifische Route angewendet wird, können Sie den Callback direkt in der Route hinzufügen:
Flight::route('/users', function() {
$db = Flight::db();
$users = $db->fetchAll("SELECT * FROM users");
Flight::render('users_table', ['users' => $users]);
// Dies wird nur die Response für diese Route gzippen
Flight::response()->addResponseBodyCallback(function($body) {
return gzencode($body, 9);
});
});
Middleware-Option
Sie können auch Middleware verwenden, um den Callback auf alle Routes über Middleware anzuwenden:
// MinifyMiddleware.php
class MinifyMiddleware {
public function before() {
// Wenden Sie den Callback hier auf das response()-Objekt an.
Flight::response()->addResponseBodyCallback(function($body) {
return $this->minify($body);
});
}
protected function minify(string $body): string {
// minimieren Sie den Body irgendwie
return $body;
}
}
// index.php
Flight::group('/users', function() {
Flight::route('', function() { /* ... */ });
Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);
Statuscodes
Sie können den Statuscode der Response mit der status-Methode setzen:
Flight::route('/@id', function($id) {
if($id == 123) {
Flight::response()->status(200);
echo "Hello, World!";
} else {
Flight::response()->status(403);
echo "Forbidden";
}
});
Wenn Sie den aktuellen Statuscode abrufen möchten, können Sie die status-Methode ohne Argumente verwenden:
Flight::response()->status(); // 200
Setzen eines Response-Headers
Sie können einen Header wie den Content-Type der Response mit der header-Methode setzen:
// Dies sendet "Hello, World!" an den Browser des Benutzers als reinen Text
Flight::route('/', function() {
Flight::response()->header('Content-Type', 'text/plain');
// oder
Flight::response()->setHeader('Content-Type', 'text/plain');
echo "Hello, World!";
});
Weiterleitung
Sie können die aktuelle Anfrage weiterleiten, indem Sie die redirect()-Methode verwenden und eine neue URL übergeben:
Flight::route('/login', function() {
$username = Flight::request()->data->username;
$password = Flight::request()->data->password;
$passwordConfirm = Flight::request()->data->password_confirm;
if($password !== $passwordConfirm) {
Flight::redirect('/new/location');
return; // dies ist notwendig, damit die Funktionalität unten nicht ausgeführt wird
}
// fügen Sie den neuen Benutzer hinzu...
Flight::db()->runQuery("INSERT INTO users ....");
Flight::redirect('/admin/dashboard');
});
Hinweis: Standardmäßig sendet Flight einen HTTP 303 ("See Other")-Statuscode. Sie können optional einen benutzerdefinierten Code setzen:
Flight::redirect('/new/location', 301); // permanent
Stoppen der Route-Ausführung
Sie können das Framework stoppen und sofort beenden, indem Sie die halt-Methode aufrufen:
Flight::halt();
Sie können auch einen optionalen HTTP-Statuscode und eine Nachricht angeben:
Flight::halt(200, 'Be right back...');
Das Aufrufen von halt verwirft alle Response-Inhalte bis zu diesem Punkt und stoppt die gesamte Ausführung. Wenn Sie das Framework stoppen und die aktuelle Response ausgeben möchten, verwenden Sie die stop-Methode:
Flight::stop($httpStatusCode = null);
Hinweis:
Flight::stop()hat einiges seltsames Verhalten, wie z.B. dass es die Response ausgibt, aber die Ausführung Ihres Skripts fortsetzt, was möglicherweise nicht das ist, was Sie wollen. Sie könnenexitoderreturnnach dem Aufruf vonFlight::stop()verwenden, um weitere Ausführung zu verhindern, aber es wird im Allgemeinen empfohlen,Flight::halt()zu verwenden.
Dies speichert den Header-Schlüssel und -Wert im Response-Objekt. Am Ende des Request-Lebenszyklus wird es die Header aufbauen und eine Response senden.
Erweiterte Verwendung
Sofortiges Senden eines Headers
Es kann Fälle geben, in denen Sie etwas Benutzerdefiniertes mit dem Header tun müssen und den Header in genau dieser Code-Zeile senden müssen, an der Sie arbeiten. Wenn Sie eine streamed route setzen, ist das, was Sie brauchen. Das ist durch response()->setRealHeader() erreichbar.
Flight::route('/', function() {
Flight::response()->setRealHeader('Content-Type: text/plain');
echo 'Streaming response...';
sleep(5);
echo 'Done!';
})->stream();
JSONP
Für JSONP-Anfragen können Sie optional den Query-Parameter-Namen übergeben, den Sie verwenden, um Ihre Callback-Funktion zu definieren:
Flight::jsonp(['id' => 123], 'q');
Also, wenn Sie eine GET-Anfrage mit ?q=my_func stellen, sollten Sie die Ausgabe erhalten:
my_func({"id":123});
Wenn Sie keinen Query-Parameter-Namen übergeben, wird standardmäßig jsonp verwendet.
Hinweis: Wenn Sie 2025 und später immer noch JSONP-Anfragen verwenden, springen Sie in den Chat und erzählen Sie uns warum! Wir lieben es, gute Kampf-/Horror-Geschichten zu hören!
Löschen von Response-Daten
Sie können den Response-Body und Header löschen, indem Sie die clear()-Methode verwenden. Dies löscht alle der Response zugewiesenen Header, löscht den Response-Body und setzt den Statuscode auf 200.
Flight::response()->clear();
Nur Response-Body löschen
Wenn Sie nur den Response-Body löschen möchten, können Sie die clearBody()-Methode verwenden:
// Dies behält immer noch alle auf dem response()-Objekt gesetzten Header.
Flight::response()->clearBody();
HTTP-Caching
Flight bietet integrierte Unterstützung für HTTP-Level-Caching. Wenn die Caching-Bedingung erfüllt ist, wird Flight eine HTTP 304 Not Modified-Response zurückgeben. Beim nächsten Mal, wenn der Client dieselbe Ressource anfordert, wird er aufgefordert, seine lokal gecachte Version zu verwenden.
Route-Level-Caching
Wenn Sie Ihre gesamte Response cachen möchten, können Sie die cache()-Methode verwenden und eine Cache-Zeit übergeben.
// Dies cached die Response für 5 Minuten
Flight::route('/news', function () {
Flight::response()->cache(time() + 300);
echo 'This content will be cached.';
});
// Alternativ können Sie einen String verwenden, den Sie an die strtotime()-Methode übergeben würden
Flight::route('/news', function () {
Flight::response()->cache('+5 minutes');
echo 'This content will be cached.';
});
Last-Modified
Sie können die lastModified-Methode verwenden und einen UNIX-Timestamp übergeben, um das Datum und die Zeit zu setzen, zu der eine Seite zuletzt geändert wurde. Der Client wird sein Cache weiterhin verwenden, bis der Last-Modified-Wert geändert wird.
Flight::route('/news', function () {
Flight::lastModified(1234567890);
echo 'This content will be cached.';
});
ETag
ETag-Caching ist ähnlich wie Last-Modified, außer dass Sie jede ID für die Ressource angeben können, die Sie möchten:
Flight::route('/news', function () {
Flight::etag('my-unique-id');
echo 'This content will be cached.';
});
Beachten Sie, dass das Aufrufen von entweder lastModified oder etag beide den Cache-Wert setzt und prüft. Wenn der Cache-Wert zwischen den Anfragen gleich ist, wird Flight sofort eine HTTP 304-Response senden und die Verarbeitung stoppen.
Herunterladen einer Datei
v3.12.0
Es gibt eine Hilfsmethode, um eine Datei an den Endbenutzer zu streamen. Sie können die download-Methode verwenden und den Pfad übergeben.
Flight::route('/download', function () {
Flight::download('/path/to/file.txt');
// Ab v3.17.1 können Sie einen benutzerdefinierten Dateinamen für das Download angeben
Flight::download('/path/to/file.txt', 'custom_name.txt');
});
Siehe auch
- Routing - Wie man Routes auf Controller abbildet und Views rendert.
- Requests - Verständnis, wie man eingehende Anfragen handhabt.
- Middleware - Verwendung von Middleware mit Routes für Authentifizierung, Logging usw.
- Warum ein Framework? - Verständnis der Vorteile der Verwendung eines Frameworks wie Flight.
- Erweitern - Wie man Flight mit eigener Funktionalität erweitert.
Fehlerbehebung
- Wenn Sie Probleme mit nicht funktionierenden Weiterleitungen haben, stellen Sie sicher, dass Sie ein
return;zur Methode hinzufügen. stop()undhalt()sind nicht dasselbe.halt()stoppt die Ausführung sofort, währendstop()die Ausführung fortsetzt.
Changelog
- v3.17.1 -
$fileNamezudownloadFile()-Methode hinzugefügt. - v3.12.0 -
downloadFile-Hilfsmethode hinzugefügt. - v3.10.0 -
jsonHalthinzugefügt. - v1.0 - Erste Veröffentlichung.
Learn/events
Event Manager
ab v3.15.0
Überblick
Events ermöglichen es Ihnen, benutzerdefiniertes Verhalten in Ihrer Anwendung zu registrieren und auszulösen. Mit der Ergänzung von Flight::onEvent() und Flight::triggerEvent() können Sie nun in Schlüssel-Momente des Lebenszyklus Ihrer App eingreifen oder eigene Events definieren (wie Benachrichtigungen und E-Mails), um Ihren Code modularer und erweiterbarer zu machen. Diese Methoden sind Teil der mappbaren Methoden von Flight, was bedeutet, dass Sie ihr Verhalten nach Bedarf überschreiben können.
Verständnis
Events erlauben es Ihnen, verschiedene Teile Ihrer Anwendung zu trennen, damit sie nicht zu stark voneinander abhängen. Diese Trennung – oft als Entkopplung bezeichnet – macht Ihren Code einfacher zu aktualisieren, zu erweitern oder zu debuggen. Anstatt alles in einem großen Block zu schreiben, können Sie Ihre Logik in kleinere, unabhängige Teile aufteilen, die auf spezifische Aktionen (Events) reagieren.
Stellen Sie sich vor, Sie bauen eine Blog-App:
- Wenn ein Benutzer einen Kommentar postet, möchten Sie möglicherweise:
- Den Kommentar in der Datenbank speichern.
- Eine E-Mail an den Blog-Besitzer senden.
- Die Aktion für Sicherheitszwecke protokollieren.
Ohne Events würden Sie all das in eine Funktion packen. Mit Events können Sie es aufteilen: Ein Teil speichert den Kommentar, ein anderer löst ein Event wie 'comment.posted' aus, und separate Listener handhaben die E-Mail und das Protokollieren. Das hält Ihren Code sauberer und ermöglicht es Ihnen, Funktionen (wie Benachrichtigungen) hinzuzufügen oder zu entfernen, ohne die Kernlogik zu berühren.
Häufige Anwendungsfälle
In den meisten Fällen eignen sich Events für Dinge, die optional sind, aber nicht zwingend ein absoluter Kernteil Ihres Systems. Zum Beispiel sind die Folgenden gut zu haben, aber wenn sie aus irgendeinem Grund fehlschlagen, sollte Ihre Anwendung immer noch funktionieren:
- Protokollierung: Aktionen wie Logins oder Fehler protokollieren, ohne den Hauptcode zu überladen.
- Benachrichtigungen: E-Mails oder Warnungen senden, wenn etwas passiert.
- Cache-Updates: Caches aktualisieren oder andere Systeme über Änderungen benachrichtigen.
Angenommen jedoch, Sie haben eine „Passwort vergessen“-Funktion. Diese sollte Teil Ihrer Kernfunktionalität sein und kein Event, da wenn diese E-Mail nicht versendet wird, der Benutzer sein Passwort nicht zurücksetzen und Ihre Anwendung nicht nutzen kann.
Grundlegende Verwendung
Das Event-System von Flight basiert auf zwei Hauptmethoden: Flight::onEvent() zum Registrieren von Event-Listenern und Flight::triggerEvent() zum Auslösen von Events. Hier ist, wie Sie sie verwenden können:
Registrieren von Event-Listenern
Um auf ein Event zu hören, verwenden Sie Flight::onEvent(). Diese Methode ermöglicht es Ihnen, zu definieren, was passieren soll, wenn ein Event auftritt.
Flight::onEvent(string $event, callable $callback): void
$event: Ein Name für Ihr Event (z. B.'user.login').$callback: Die Funktion, die ausgeführt wird, wenn das Event ausgelöst wird.
Sie „abonnieren“ ein Event, indem Sie Flight mitteilen, was es tun soll, wenn es passiert. Der Callback kann Argumente akzeptieren, die vom Event-Auslöser übergeben werden.
Das Event-System von Flight ist synchron, was bedeutet, dass jeder Event-Listener nacheinander ausgeführt wird. Wenn Sie ein Event auslösen, werden alle registrierten Listener für dieses Event vollständig ausgeführt, bevor Ihr Code fortfährt. Dies ist wichtig zu verstehen, da es sich von asynchronen Event-Systemen unterscheidet, bei denen Listener parallel oder zu einem späteren Zeitpunkt ausgeführt werden könnten.
Einfaches Beispiel
Flight::onEvent('user.login', function ($username) {
echo "Willkommen zurück, $username!";
// Sie können eine E-Mail senden, wenn der Login von einem neuen Standort kommt
});
Hier, wenn das 'user.login'-Event ausgelöst wird, begrüßt es den Benutzer namentlich und könnte auch Logik enthalten, um eine E-Mail zu senden, falls nötig.
Hinweis: Der Callback kann eine Funktion, eine anonyme Funktion oder eine Methode aus einer Klasse sein.
Auslösen von Events
Um ein Event auszulösen, verwenden Sie Flight::triggerEvent(). Dies weist Flight an, alle für dieses Event registrierten Listener auszuführen und dabei alle von Ihnen bereitgestellten Daten weiterzuleiten.
Flight::triggerEvent(string $event, ...$args): void
$event: Der Name des Events, das Sie auslösen (muss zu einem registrierten Event passen)....$args: Optionale Argumente, die an die Listener gesendet werden (kann jede Anzahl von Argumenten sein).
Einfaches Beispiel
$username = 'alice';
Flight::triggerEvent('user.login', $username);
Dies löst das 'user.login'-Event aus und sendet 'alice' an den Listener, den wir zuvor definiert haben, was ausgibt: Willkommen zurück, alice!.
- Wenn keine Listener registriert sind, passiert nichts – Ihre App bricht nicht ab.
- Verwenden Sie den Spread-Operator (
...), um mehrere Argumente flexibel zu übergeben.
Stoppen von Events
Wenn ein Listener false zurückgibt, werden keine weiteren Listener für dieses Event ausgeführt. Dies ermöglicht es Ihnen, die Event-Kette basierend auf spezifischen Bedingungen zu stoppen. Denken Sie daran, dass die Reihenfolge der Listener wichtig ist, da der erste, der false zurückgibt, den Rest stoppt.
Beispiel:
Flight::onEvent('user.login', function ($username) {
if (isBanned($username)) {
logoutUser($username);
return false; // Stoppt nachfolgende Listener
}
});
Flight::onEvent('user.login', function ($username) {
sendWelcomeEmail($username); // Dies wird nie gesendet
});
Überschreiben von Event-Methoden
Flight::onEvent() und Flight::triggerEvent() können erweitert werden, was bedeutet, dass Sie definieren können, wie sie funktionieren. Das ist großartig für fortgeschrittene Benutzer, die das Event-System anpassen möchten, z. B. durch Hinzufügen von Protokollierung oder Änderung der Event-Verteilung.
Beispiel: Anpassen von onEvent
Flight::map('onEvent', function (string $event, callable $callback) {
// Protokolliere jede Event-Registrierung
error_log("Neuer Event-Listener hinzugefügt für: $event");
// Rufe das Standardverhalten auf (angenommen ein internes Event-System)
Flight::_onEvent($event, $callback);
});
Jetzt wird jedes Mal, wenn Sie ein Event registrieren, protokolliert, bevor es fortgesetzt wird.
Warum überschreiben?
- Debugging oder Überwachung hinzufügen.
- Events in bestimmten Umgebungen einschränken (z. B. in Tests deaktivieren).
- Mit einer anderen Event-Bibliothek integrieren.
Wo Events platzieren
Wenn Sie neu in den Event-Konzepten in Ihrem Projekt sind, fragen Sie sich vielleicht: Wo registriere ich all diese Events in meiner App? Die Einfachheit von Flight bedeutet, dass es keine strenge Regel gibt – Sie können sie überall platzieren, wo es für Ihr Projekt Sinn macht. Allerdings hilft es, sie organisiert zu halten, um Ihren Code zu pflegen, wenn Ihre App wächst. Hier sind einige praktische Optionen und Best Practices, angepasst an die leichte Natur von Flight:
Option 1: In Ihrer Haupt-index.php
Für kleine Apps oder schnelle Prototypen können Sie Events direkt in Ihrer index.php-Datei neben Ihren Routen registrieren. Das hält alles an einem Ort, was in Ordnung ist, wenn Einfachheit Ihre Priorität ist.
require 'vendor/autoload.php';
// Events registrieren
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
// Routen definieren
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Vorteile: Einfach, keine extra Dateien, super für kleine Projekte.
- Nachteile: Kann unübersichtlich werden, wenn Ihre App mit mehr Events und Routen wächst.
Option 2: Eine separate events.php-Datei
Für eine etwas größere App ziehen Sie in Erwägung, Event-Registrierungen in eine dedizierte Datei wie app/config/events.php zu verschieben. Schließen Sie diese Datei in Ihrer index.php vor Ihren Routen ein. Das ahmt nach, wie Routen oft in app/config/routes.php in Flight-Projekten organisiert werden.
// app/config/events.php
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in at " . date('Y-m-d H:i:s'));
});
Flight::onEvent('user.registered', function ($email, $name) {
echo "Email sent to $email: Welcome, $name!";
});
// index.php
require 'vendor/autoload.php';
require 'app/config/events.php';
Flight::route('/login', function () {
$username = 'bob';
Flight::triggerEvent('user.login', $username);
echo "Logged in!";
});
Flight::start();
- Vorteile: Hält
index.phpauf Routing fokussiert, organisiert Events logisch, einfach zu finden und zu bearbeiten. - Nachteile: Fügt eine kleine Struktur hinzu, die für sehr kleine Apps übertrieben wirken könnte.
Option 3: Nahe dem Auslöseort
Ein anderer Ansatz ist, Events nahe dem Ort zu registrieren, an dem sie ausgelöst werden, z. B. in einem Controller oder Routen-Definition. Das funktioniert gut, wenn ein Event spezifisch für einen Teil Ihrer App ist.
Flight::route('/signup', function () {
// Event hier registrieren
Flight::onEvent('user.registered', function ($email) {
echo "Welcome email sent to $email!";
});
$email = 'jane@example.com';
Flight::triggerEvent('user.registered', $email);
echo "Signed up!";
});
- Vorteile: Hält verwandten Code zusammen, gut für isolierte Features.
- Nachteile: Verteilt Event-Registrierungen, was es schwieriger macht, alle Events auf einen Blick zu sehen; Risiko von Duplikaten, wenn nicht vorsichtig.
Best Practice für Flight
- Einfach starten: Für winzige Apps platzieren Sie Events in
index.php. Es ist schnell und passt zu Flights Minimalismus. - Intelligent wachsen: Wenn Ihre App expandiert (z. B. mehr als 5-10 Events), verwenden Sie eine
app/config/events.php-Datei. Es ist ein natürlicher Schritt, wie die Organisation von Routen, und hält Ihren Code ordentlich, ohne komplexe Frameworks hinzuzufügen. - Über-Engineering vermeiden: Erstellen Sie keine vollständige „Event-Manager“-Klasse oder -Verzeichnis, es sei denn, Ihre App wird riesig – Flight lebt von Einfachheit, also halten Sie es leichtgewichtig.
Tipp: Nach Zweck gruppieren
In events.php gruppieren Sie verwandte Events (z. B. alle benutzerbezogenen Events zusammen) mit Kommentaren für Klarheit:
// app/config/events.php
// User Events
Flight::onEvent('user.login', function ($username) {
error_log("$username logged in");
});
Flight::onEvent('user.registered', function ($email) {
echo "Welcome to $email!";
});
// Page Events
Flight::onEvent('page.updated', function ($pageId) {
Flight::cache()->delete("page_$pageId");
});
Diese Struktur skaliert gut und bleibt anfängerfreundlich.
Beispiele aus der Praxis
Lassen Sie uns einige reale Szenarien durchgehen, um zu zeigen, wie Events funktionieren und warum sie hilfreich sind.
Beispiel 1: Protokollieren eines Benutzer-Logins
// Schritt 1: Einen Listener registrieren
Flight::onEvent('user.login', function ($username) {
$time = date('Y-m-d H:i:s');
error_log("$username logged in at $time");
});
// Schritt 2: Es in Ihrer App auslösen
Flight::route('/login', function () {
$username = 'bob'; // Stellen Sie sich vor, das kommt aus einem Formular
Flight::triggerEvent('user.login', $username);
echo "Hi, $username!";
});
Warum nützlich: Der Login-Code muss nichts über Protokollierung wissen – er löst nur das Event aus. Sie können später mehr Listener hinzufügen (z. B. eine Willkommens-E-Mail senden), ohne die Route zu ändern.
Beispiel 2: Benachrichtigen über neue Benutzer
// Listener für neue Registrierungen
Flight::onEvent('user.registered', function ($email, $name) {
// Simuliere das Senden einer E-Mail
echo "Email sent to $email: Welcome, $name!";
});
// Auslösen, wenn jemand registriert
Flight::route('/signup', function () {
$email = 'jane@example.com';
$name = 'Jane';
Flight::triggerEvent('user.registered', $email, $name);
echo "Thanks for signing up!";
});
Warum nützlich: Die Registrierungslogik konzentriert sich auf die Erstellung des Benutzers, während das Event Benachrichtigungen handhabt. Sie könnten später mehr Listener hinzufügen (z. B. die Registrierung protokollieren).
Beispiel 3: Cache leeren
// Listener zum Leeren eines Caches
Flight::onEvent('page.updated', function ($pageId) {
// Wenn Sie das flightphp/cache-Plugin verwenden
Flight::cache()->delete("page_$pageId");
echo "Cache cleared for page $pageId.";
});
// Auslösen, wenn eine Seite bearbeitet wird
Flight::route('/edit-page/(@id)', function ($pageId) {
// Stellen Sie sich vor, wir haben die Seite aktualisiert
Flight::triggerEvent('page.updated', $pageId);
echo "Page $pageId updated.";
});
Warum nützlich: Der Bearbeitungscode kümmert sich nicht um Caching – er signalisiert nur die Aktualisierung. Andere Teile der App können entsprechend reagieren.
Best Practices
- Events klar benennen: Verwenden Sie spezifische Namen wie
'user.login'oder'page.updated', damit klar ist, was sie tun. - Listener einfach halten: Legen Sie keine langsamen oder komplexen Aufgaben in Listener – halten Sie Ihre App schnell.
- Ihre Events testen: Lösen Sie sie manuell aus, um sicherzustellen, dass Listener wie erwartet funktionieren.
- Events weise verwenden: Sie sind großartig für Entkopplung, aber zu viele können Ihren Code schwer nachvollziehbar machen – verwenden Sie sie, wenn es Sinn macht.
Das Event-System in Flight PHP mit Flight::onEvent() und Flight::triggerEvent() bietet Ihnen eine einfache, aber leistungsstarke Möglichkeit, flexible Anwendungen zu bauen. Indem verschiedene Teile Ihrer App durch Events miteinander kommunizieren, können Sie Ihren Code organisiert, wiederverwendbar und einfach erweiterbar halten. Ob Sie Aktionen protokollieren, Benachrichtigungen senden oder Updates verwalten – Events helfen Ihnen dabei, ohne Ihre Logik zu verknüpfen. Und mit der Möglichkeit, diese Methoden zu überschreiben, haben Sie die Freiheit, das System an Ihre Bedürfnisse anzupassen. Starten Sie klein mit einem einzelnen Event und beobachten Sie, wie es die Struktur Ihrer App verändert!
Eingebauten Events
Flight PHP kommt mit einigen eingebauten Events, die Sie verwenden können, um in den Lebenszyklus des Frameworks einzugreifen. Diese Events werden an spezifischen Punkten im Request/Response-Zyklus ausgelöst und ermöglichen es Ihnen, benutzerdefinierte Logik auszuführen, wenn bestimmte Aktionen auftreten.
Liste der eingebauten Events
- flight.request.received:
function(Request $request)Wird ausgelöst, wenn eine Anfrage empfangen, geparst und verarbeitet wird. - flight.error:
function(Throwable $exception)Wird ausgelöst, wenn ein Fehler während des Request-Lebenszyklus auftritt. - flight.redirect:
function(string $url, int $status_code)Wird ausgelöst, wenn eine Weiterleitung initiiert wird. - flight.cache.checked:
function(string $cache_key, bool $hit, float $executionTime)Wird ausgelöst, wenn der Cache für einen spezifischen Schlüssel überprüft wird und ob es ein Treffer oder Fehlschlag war. - flight.middleware.before:
function(Route $route)Wird ausgelöst, nachdem das Before-Middleware ausgeführt wurde. - flight.middleware.after:
function(Route $route)Wird ausgelöst, nachdem das After-Middleware ausgeführt wurde. - flight.middleware.executed:
function(Route $route, $middleware, string $method, float $executionTime)Wird ausgelöst, nachdem ein beliebiges Middleware ausgeführt wurde. - flight.route.matched:
function(Route $route)Wird ausgelöst, wenn eine Route übereinstimmt, aber noch nicht ausgeführt wird. - flight.route.executed:
function(Route $route, float $executionTime)Wird ausgelöst, nachdem eine Route ausgeführt und verarbeitet wurde.$executionTimeist die Zeit, die es gedauert hat, die Route auszuführen (Controller aufrufen usw.). - flight.view.rendered:
function(string $template_file_path, float $executionTime)Wird ausgelöst, nachdem eine View gerendert wurde.$executionTimeist die Zeit, die es gedauert hat, das Template zu rendern. Hinweis: Wenn Sie dierender-Methode überschreiben, müssen Sie dieses Event erneut auslösen. - flight.response.sent:
function(Response $response, float $executionTime)Wird ausgelöst, nachdem eine Response an den Client gesendet wurde.$executionTimeist die Zeit, die es gedauert hat, die Response zu erstellen.
Siehe auch
- Erweitern von Flight - Wie Sie die Kernfunktionalität von Flight erweitern und anpassen.
- Cache - Beispiel für die Verwendung von Events, um Cache zu leeren, wenn eine Seite aktualisiert wird.
Fehlerbehebung
- Wenn Ihre Event-Listener nicht aufgerufen werden, stellen Sie sicher, dass Sie sie vor dem Auslösen der Events registrieren. Die Reihenfolge der Registrierung ist wichtig.
Änderungsprotokoll
- v3.15.0 - Events zu Flight hinzugefügt.
Learn/templates
HTML-Ansichten und Vorlagen
Überblick
Flight bietet standardmäßig eine grundlegende HTML-Templating-Funktionalität. Templating ist eine sehr effektive Methode, um deine Anwendungslogik von der Darstellungsebene zu entkoppeln. Eine dedizierte Engine (Twig, Latte usw.) gibt KI-Programmierwerkzeugen außerdem eine vertraute, eingeschränkte Syntax, sodass sie weniger wahrscheinlich Geschäftslogik in dein HTML einfügen.
Verständnis
Wenn du eine Anwendung entwickelst, wirst du wahrscheinlich HTML haben, das du an den Endbenutzer ausliefern möchtest. PHP selbst ist eine Templating-Sprache, aber es ist sehr einfach, Geschäftslogik wie Datenbankaufrufe, API-Aufrufe usw. in deine HTML-Datei zu packen und das Testen und Entkoppeln zu einem sehr schwierigen Prozess zu machen. Indem du Daten in eine Vorlage schiebst und die Vorlage sich selbst rendern lässt, wird es viel einfacher, deinen Code zu entkoppeln und Unit-Tests zu unterziehen. Du wirst uns danken, wenn du Vorlagen verwendest!
Grundlegende Verwendung
Flight ermöglicht es dir, die Standard-View-Engine auszutauschen, indem du einfach render zuordnest (oder eine View-Klasse registrierst). Scrolle nach unten für Twig, Latte, Smarty, Blade und mehr.
Skeleton-Standard: Das offizielle flightphp/skeleton verwendet nur Twig unter
app/views/(*.twig). Controller rufen$this->app->render('welcome', $data)auf (Endung optional). Das ist eine Anwendungsentscheidung für neue Projekte – keine Anforderung des Flight-Kerns. Latte und andere Engines werden weiterhin vollständig unterstützt.
Twig
Skeleton-Standard
Twig ist eine flexible, schnelle und sichere Template-Engine, die von Symfony und vielen anderen PHP-Projekten verwendet wird. KI-Programmierwerkzeuge kennen Twig tendenziell besonders gut, und es maskiert Ausgaben standardmäßig automatisch, was vor XSS schützt.
Installation
composer require twig/twig
(Bereits enthalten, wenn du composer create-project flightphp/skeleton ausführst.)
Grundlegende Konfiguration
Überschreibe die render-Methode, um Twig anstelle des standardmäßigen PHP-Renderers zu verwenden:
// Überschreibe die render-Methode, um Twig anstelle des standardmäßigen PHP-Renderers zu verwenden
Flight::map('render', function(string $template, array $data): void {
$loader = new \Twig\Loader\FilesystemLoader(Flight::get('flight.views.path'));
$twig = new \Twig\Environment($loader, [
// Wo Twig seine kompilierten Vorlagen speichert
'cache' => __DIR__ . '/../cache/twig',
'auto_reload' => true,
]);
// Erlaubt "welcome" oder "welcome.twig"
if (substr($template, -5) !== '.twig') {
$template .= '.twig';
}
echo $twig->render($template, $data);
});
Im Skeleton befindet sich diese Verdrahtung in app/config/services.php (gemeinsame Twig-Umgebung, Cache-Pfad, globale Variablen wie base_url / CSP-Nonce). Bevorzuge es, Engine zu injizieren und $app->render() aus Controllern aufzurufen, damit der Code KI- und testfreundlich bleibt.
Twig in Flight verwenden
Jetzt, da du mit Twig rendern kannst, kannst du Folgendes tun:
{# app/views/home.twig #}
<html>
<head>
<title>{% if title %}{{ title }} - {% endif %}Meine App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hallo, {{ name }}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.twig', [
'title' => 'Homepage',
'name' => $name
]);
});
Wenn du in deinem Browser /Bob aufrufst, wäre die Ausgabe:
<html>
<head>
<title>Homepage - Meine App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hallo, Bob!</h1>
</body>
</html>
Weiterführende Informationen
Ein vollständigeres Beispiel für die Verwendung von Twig mit Layouts findest du im Abschnitt Klasse-Plugins dieser Dokumentation. Für Renderzeit-Metriken auf der Tracy-Leiste siehe das Twig-Panel in Tracy Extensions.
Du kannst mehr über die vollständigen Fähigkeiten von Twig erfahren, indem du die offizielle Dokumentation liest.
Latte
gute Alternative
Latte ist eine voll ausgestattete Engine mit einer PHP-ähnlichen Syntax. Sie ist nach wie vor eine ausgezeichnete Wahl für Flight-Anwendungen; das Skeleton standardisiert lediglich auf Twig als gemeinsamen Standard (besonders hilfreich, wenn KI-Werkzeuge Vorlagen generieren).
Installation
composer require latte/latte
Grundlegende Konfiguration
Die Hauptidee ist, die render-Methode zu überschreiben, um Latte anstelle des standardmäßigen PHP-Renderers zu verwenden.
// Überschreibe die render-Methode, um Latte anstelle des standardmäßigen PHP-Renderers zu verwenden
Flight::map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Wo Latte seinen Cache speichert
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Latte in Flight verwenden
Jetzt, da du mit Latte rendern kannst, kannst du Folgendes tun:
<!-- app/views/home.latte -->
<html>
<head>
<title>{$title ? $title . ' - '}Meine App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hallo, {$name}!</h1>
</body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
Flight::render('home.latte', [
'title' => 'Homepage',
'name' => $name
]);
});
Wenn du in deinem Browser /Bob aufrufst, wäre die Ausgabe:
<html>
<head>
<title>Homepage - Meine App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Hallo, Bob!</h1>
</body>
</html>
Weiterführende Informationen
Ein komplexeres Beispiel für die Verwendung von Latte mit Layouts findest du im Abschnitt Klasse-Plugins dieser Dokumentation.
Du kannst mehr über die vollständigen Fähigkeiten von Latte einschließlich Übersetzungs- und Sprachfunktionen erfahren, indem du die offizielle Dokumentation liest.
Integrierte View-Engine
veraltet
Hinweis: Auch wenn dies weiterhin die Standardfunktionalität ist und technisch weiterhin funktioniert.
Um eine View-Vorlage anzuzeigen, rufe die render-Methode mit dem Namen der Vorlagendatei und optionalen Vorlagendaten auf:
Flight::render('hello.php', ['name' => 'Bob']);
Die übergebenen Vorlagendaten werden automatisch in die Vorlage injiziert und können wie eine lokale Variable referenziert werden. Vorlagendateien sind einfach PHP-Dateien. Wenn der Inhalt der Vorlagendatei hello.php ist:
Hallo, <?= $name ?>!
Die Ausgabe wäre:
Hallo, Bob!
Du kannst Ansichtsvariablen auch manuell mit der set-Methode festlegen:
Flight::view()->set('name', 'Bob');
Die Variable name ist nun in allen deinen Ansichten verfügbar. Du kannst also einfach Folgendes tun:
Flight::render('hello');
Beachte, dass du bei der Angabe des Namens der Vorlage in der render-Methode die Erweiterung .php weglassen kannst.
Standardmäßig sucht Flight in einem views-Verzeichnis nach Vorlagendateien. Du kannst einen alternativen Pfad für deine Vorlagen festlegen, indem du die folgende Konfiguration setzt:
Flight::set('flight.views.path', '/pfad/zu/views');
Layouts
Es ist üblich, dass Websites eine einzige Layout-Vorlagendatei mit wechselndem Inhalt haben. Um Inhalte zu rendern, die in einem Layout verwendet werden sollen, kannst du einen optionalen Parameter an die render-Methode übergeben.
Flight::render('header', ['heading' => 'Hallo'], 'headerContent');
Flight::render('body', ['body' => 'Welt'], 'bodyContent');
Deine Ansicht enthält dann gespeicherte Variablen namens headerContent und bodyContent. Du kannst dann dein Layout rendern, indem du Folgendes tust:
Flight::render('layout', ['title' => 'Homepage']);
Wenn die Vorlagendateien wie folgt aussehen:
header.php:
<h1><?= $heading ?></h1>
body.php:
<div><?= $body ?></div>
layout.php:
<html>
<head>
<title><?= $title ?></title>
</head>
<body>
<?= $headerContent ?>
<?= $bodyContent ?>
</body>
</html>
Die Ausgabe wäre:
<html>
<head>
<title>Homepage</title>
</head>
<body>
<h1>Hallo</h1>
<div>Welt</div>
</body>
</html>
Smarty
So verwendest du die Smarty-Template-Engine für deine Ansichten:
// Lade die Smarty-Bibliothek
require './Smarty/libs/Smarty.class.php';
// Registriere Smarty als View-Klasse
// Übergebe außerdem eine Callback-Funktion, um Smarty beim Laden zu konfigurieren
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
$smarty->setTemplateDir('./templates/');
$smarty->setCompileDir('./templates_c/');
$smarty->setConfigDir('./config/');
$smarty->setCacheDir('./cache/');
});
// Weise Vorlagendaten zu
Flight::view()->assign('name', 'Bob');
// Zeige die Vorlage an
Flight::view()->display('hello.tpl');
Der Vollständigkeit halber solltest du auch die standardmäßige render-Methode von Flight überschreiben:
Flight::map('render', function(string $template, array $data): void {
Flight::view()->assign($data);
Flight::view()->display($template);
});
Blade
So verwendest du die Blade-Template-Engine für deine Ansichten:
Zuerst musst du die BladeOne-Bibliothek über Composer installieren:
composer require eftec/bladeone
Dann kannst du BladeOne als View-Klasse in Flight konfigurieren:
<?php
// Lade die BladeOne-Bibliothek
use eftec\bladeone\BladeOne;
// Registriere BladeOne als View-Klasse
// Übergebe außerdem eine Callback-Funktion, um BladeOne beim Laden zu konfigurieren
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
$views = __DIR__ . '/../views';
$cache = __DIR__ . '/../cache';
$blade->setPath($views);
$blade->setCompiledPath($cache);
});
// Weise Vorlagendaten zu
Flight::view()->share('name', 'Bob');
// Zeige die Vorlage an
echo Flight::view()->run('hello', []);
Der Vollständigkeit halber solltest du auch die standardmäßige render-Methode von Flight überschreiben:
<?php
Flight::map('render', function(string $template, array $data): void {
echo Flight::view()->run($template, $data);
});
In diesem Beispiel könnte die Vorlagendatei hello.blade.php wie folgt aussehen:
<?php
Hallo, {{ $name }}!
Die Ausgabe wäre:
Hallo, Bob!
Siehe auch
- Installation – Skeleton-Layout (
app/views/*.twig) für neue Projekte. - Erweitern – So überschreibst du die
render-Methode, um eine andere Template-Engine zu verwenden. - Routing – So ordnest du Routen Controllern zu und renderst Ansichten.
- Antworten – So passt du HTTP-Antworten an.
- Sicherheit – Automatisches Maskieren und XSS.
- KI & Entwicklererfahrung – Warum eine Standard-View-Engine Programmieragenten hilft.
- Warum ein Framework? – Wie Vorlagen in das Gesamtbild passen.
Fehlerbehebung
- Wenn du eine Weiterleitung in deiner Middleware hast, aber deine App scheinbar nicht weiterleitet, stelle sicher, dass du eine
exit;-Anweisung in deiner Middleware hinzufügst. - Wenn Twig eine Vorlage nicht finden kann, überprüfe
flight.views.pathund dass die Datei unter diesem Pfad mit der erwarteten Erweiterung existiert (Skeleton:app/views/).
Änderungsprotokoll
- Doku – Twig als offizieller Skeleton-Standard dokumentiert; Latte bleibt eine erstklassige Alternative.
- v2.0 – Erste Veröffentlichung.
Learn/simple_pdo
SimplePdo PDO-Hilfsklasse
Überblick
Die SimplePdo-Klasse in Flight ist eine moderne, funktionsreiche Hilfsklasse für die Arbeit mit Datenbanken unter Verwendung von PDO. Sie erweitert PdoWrapper und fügt bequeme Hilfsmethoden für gängige Datenbankoperationen wie insert(), update(), delete() und Transaktionen hinzu. Sie vereinfacht Datenbankaufgaben, gibt Ergebnisse als Collections zurück für einfachen Zugriff und unterstützt Abfrageprotokollierung und Anwendungsleistungsüberwachung (APM) für fortgeschrittene Anwendungsfälle.
Verständnis
Die SimplePdo-Klasse ist so konzipiert, dass die Arbeit mit Datenbanken in PHP viel einfacher wird. Statt mit vorbereiteten Anweisungen, Abrufmodi und ausführlichen SQL-Operationen zu jonglieren, erhalten Sie saubere, einfache Methoden für gängige Aufgaben. Jede Zeile wird als Collection zurückgegeben, sodass Sie sowohl Array-Notation ($row['name']) als auch Objekt-Notation ($row->name) verwenden können.
Diese Klasse ist eine Übersetzung von PdoWrapper, was bedeutet, dass sie alle Funktionen von PdoWrapper plus zusätzliche Hilfsmethoden enthält, die Ihren Code sauberer und wartbarer machen. Wenn Sie derzeit PdoWrapper verwenden, ist das Upgrade auf SimplePdo unkompliziert, da es PdoWrapper erweitert.
Sie können SimplePdo als geteilten Dienst in Flight registrieren und es dann überall in Ihrer App über Flight::db() verwenden.
Grundlegende Verwendung
Registrierung von SimplePdo
Registrieren Sie zuerst die SimplePdo-Klasse bei Flight:
Flight::register('db', \flight\database\SimplePdo::class, [
'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'utf8mb4\'',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
]);
HINWEIS
Wenn Sie
PDO::ATTR_DEFAULT_FETCH_MODEnicht angeben, setztSimplePdoes automatisch aufPDO::FETCH_ASSOCfür Sie.
Nun können Sie Flight::db() überall verwenden, um Ihre Datenbankverbindung zu erhalten.
Ausführen von Abfragen
runQuery()
function runQuery(string $sql, array $params = []): PDOStatement
Verwenden Sie dies für INSERTs, UPDATEs oder wenn Sie Ergebnisse manuell abrufen möchten:
$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
// $row is an array
}
Sie können es auch für Schreibvorgänge verwenden:
$db->runQuery("INSERT INTO users (name) VALUES (?)", ['Alice']);
$db->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 1]);
fetchField()
function fetchField(string $sql, array $params = []): mixed
Einen einzelnen Wert aus der Datenbank abrufen:
$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);
fetchRow()
function fetchRow(string $sql, array $params = []): ?Collection
Eine einzelne Zeile als Collection (Array-/Objektzugriff) abrufen:
$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// or
echo $user->name;
TIP
SimplePdofügt automatischLIMIT 1zufetchRow()-Abfragen hinzu, falls es noch nicht vorhanden ist, was Ihre Abfragen effizienter macht.
fetchAll()
function fetchAll(string $sql, array $params = []): array<Collection>
Alle Zeilen als Array von Collections abrufen:
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
echo $user['name'];
// or
echo $user->name;
}
fetchColumn()
function fetchColumn(string $sql, array $params = []): array
Eine einzelne Spalte als Array abrufen:
$ids = Flight::db()->fetchColumn("SELECT id FROM users WHERE active = ?", [1]);
// Returns: [1, 2, 3, 4, 5]
fetchPairs()
function fetchPairs(string $sql, array $params = []): array
Ergebnisse als Schlüssel-Wert-Paare abrufen (erste Spalte als Schlüssel, zweite als Wert):
$userNames = Flight::db()->fetchPairs("SELECT id, name FROM users");
// Returns: [1 => 'John', 2 => 'Jane', 3 => 'Bob']
Verwendung von IN()-Platzhaltern
Sie können einen einzelnen ? in einer IN()-Klausel verwenden und ein Array übergeben:
$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
Hilfsmethoden
Einer der Hauptvorteile von SimplePdo gegenüber PdoWrapper ist die Hinzufügung bequemer Hilfsmethoden für gängige Datenbankoperationen.
insert()
function insert(string $table, array $data): string
Eine oder mehrere Zeilen einfügen und die letzte Einfüge-ID zurückgeben.
Einzelner Einfügevorgang:
$id = Flight::db()->insert('users', [
'name' => 'John',
'email' => 'john@example.com'
]);
Massen-Einfügevorgang:
$id = Flight::db()->insert('users', [
['name' => 'John', 'email' => 'john@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com'],
]);
update()
function update(string $table, array $data, string $where, array $whereParams = []): int
Zeilen aktualisieren und die Anzahl der betroffenen Zeilen zurückgeben:
$affected = Flight::db()->update(
'users',
['name' => 'Jane', 'email' => 'jane@example.com'],
'id = ?',
[1]
);
HINWEIS
SQLite's
rowCount()gibt die Anzahl der Zeilen zurück, in denen Daten tatsächlich geändert wurden. Wenn Sie eine Zeile mit denselben Werten aktualisieren, die sie bereits hat, gibtrowCount()0 zurück. Dies unterscheidet sich vom Verhalten von MySQL bei der Verwendung vonPDO::MYSQL_ATTR_FOUND_ROWS.
delete()
function delete(string $table, string $where, array $whereParams = []): int
Zeilen löschen und die Anzahl der gelöschten Zeilen zurückgeben:
$deleted = Flight::db()->delete('users', 'id = ?', [1]);
transaction()
function transaction(callable $callback): mixed
Einen Callback innerhalb einer Transaktion ausführen. Die Transaktion wird automatisch bei Erfolg committet oder bei Fehler zurückgerollt:
$result = Flight::db()->transaction(function($db) {
$db->insert('users', ['name' => 'John']);
$db->insert('logs', ['action' => 'user_created']);
return $db->lastInsertId();
});
Wenn innerhalb des Callbacks eine Ausnahme geworfen wird, wird die Transaktion automatisch zurückgerollt und die Ausnahme erneut geworfen.
Fortgeschrittene Verwendung
Abfrageprotokollierung & APM
Wenn Sie die Abfrageleistung verfolgen möchten, aktivieren Sie die APM-Verfolgung bei der Registrierung:
Flight::register('db', \flight\database\SimplePdo::class, [
'mysql:host=localhost;dbname=cool_db_name',
'user',
'pass',
[/* PDO options */],
[
'trackApmQueries' => true,
'maxQueryMetrics' => 1000
]
]);
Nach dem Ausführen von Abfragen können Sie sie manuell protokollieren, aber das APM protokolliert sie automatisch, wenn aktiviert:
Flight::db()->logQueries();
Dies löst ein Ereignis (flight.db.queries) mit Verbindungs- und Abfragemetriken aus, das Sie mit Flights Ereignissystem abhören können.
Vollständiges Beispiel
Flight::route('/users', function () {
// Get all users
$users = Flight::db()->fetchAll('SELECT * FROM users');
// Stream all users
$statement = Flight::db()->runQuery('SELECT * FROM users');
while ($user = $statement->fetch()) {
echo $user['name'];
}
// Get a single user
$user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);
// Get a single value
$count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');
// Get a single column
$ids = Flight::db()->fetchColumn('SELECT id FROM users');
// Get key-value pairs
$userNames = Flight::db()->fetchPairs('SELECT id, name FROM users');
// Special IN() syntax
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE id IN (?)', [[1,2,3,4,5]]);
// Insert a new user
$id = Flight::db()->insert('users', [
'name' => 'Bob',
'email' => 'bob@example.com'
]);
// Bulk insert users
Flight::db()->insert('users', [
['name' => 'Bob', 'email' => 'bob@example.com'],
['name' => 'Jane', 'email' => 'jane@example.com']
]);
// Update a user
$affected = Flight::db()->update('users', ['name' => 'Bob'], 'id = ?', [123]);
// Delete a user
$deleted = Flight::db()->delete('users', 'id = ?', [123]);
// Use a transaction
$result = Flight::db()->transaction(function($db) {
$db->insert('users', ['name' => 'John', 'email' => 'john@example.com']);
$db->insert('audit_log', ['action' => 'user_created']);
return $db->lastInsertId();
});
});
Migration von PdoWrapper
Wenn Sie derzeit PdoWrapper verwenden, ist die Migration zu SimplePdo unkompliziert:
-
Aktualisieren Sie Ihre Registrierung:
// Old Flight::register('db', \flight\database\PdoWrapper::class, [ /* ... */ ]); // New Flight::register('db', \flight\database\SimplePdo::class, [ /* ... */ ]); -
Alle bestehenden
PdoWrapper-Methoden funktionieren inSimplePdo- Es gibt keine Breaking Changes. Ihr bestehender Code wird weiterhin funktionieren. -
Optional die neuen Hilfsmethoden verwenden - Beginnen Sie mit
insert(),update(),delete()undtransaction(), um Ihren Code zu vereinfachen.
Siehe auch
- Collections - Erfahren Sie, wie Sie die Collection-Klasse für einfachen Datenzugriff verwenden.
- PdoWrapper - Die veraltete PDO-Hilfsklasse (deprecated).
Fehlerbehebung
- Wenn Sie einen Fehler bezüglich der Datenbankverbindung erhalten, überprüfen Sie Ihre DSN, Benutzername, Passwort und Optionen.
- Alle Zeilen werden als Collections zurückgegeben – wenn Sie ein einfaches Array benötigen, verwenden Sie
$collection->getData(). - Für
IN (?)-Abfragen stellen Sie sicher, dass Sie ein Array übergeben. - Wenn Sie Speicherprobleme mit der Abfrageprotokollierung in lang laufenden Prozessen haben, passen Sie die
maxQueryMetrics-Option an.
Changelog
- v3.18.0 - Erste Veröffentlichung von SimplePdo mit Hilfsmethoden für insert, update, delete und Transaktionen.
Learn/collections
Sammlungen
Übersicht
Die Collection-Klasse in Flight ist ein praktisches Hilfsmittel zur Verwaltung von Datensätzen. Sie ermöglicht den Zugriff auf Daten sowohl mit Array- als auch mit Objektnotation, was deinen Code sauberer und flexibler macht.
Verständnis
Eine Collection ist im Grunde ein Wrapper um ein Array, aber mit einigen zusätzlichen Funktionen. Du kannst sie wie ein Array verwenden, darüber iterieren, die Anzahl der Elemente zählen und sogar auf Elemente zugreifen, als wären es Objekteigenschaften. Das ist besonders nützlich, wenn du strukturierte Daten in deiner App weitergeben möchtest oder wenn du deinen Code etwas lesbarer machen willst.
Collections implementieren mehrere PHP-Schnittstellen:
ArrayAccess(damit du Array-Syntax verwenden kannst)Iterator(damit du mitforeachiterieren kannst)Countable(damit ducount()verwenden kannst)JsonSerializable(damit du einfach in JSON konvertieren kannst)
Grundlegende Verwendung
Eine Collection erstellen
Du kannst eine Collection erstellen, indem du einfach ein Array an den Konstruktor übergibst:
use flight\util\Collection;
$data = [
'name' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$collection = new Collection($data);
Auf Elemente zugreifen
Du kannst auf Elemente entweder mit Array- oder Objektnotation zugreifen:
// Array-Notation
echo $collection['name']; // Ausgabe: FlightPHP
// Objekt-Notation
echo $collection->version; // Ausgabe: 3
Wenn du versuchst, auf einen Schlüssel zuzugreifen, der nicht existiert, erhältst du null anstatt eines Fehlers.
Elemente setzen
Du kannst Elemente ebenfalls mit beiden Notationen setzen:
// Array-Notation
$collection['author'] = 'Mike Cao';
// Objekt-Notation
$collection->license = 'MIT';
Vorhandensein prüfen und Elemente entfernen
Prüfen, ob ein Element existiert:
if (isset($collection['name'])) {
// Etwas tun
}
if (isset($collection->version)) {
// Etwas tun
}
Ein Element entfernen:
unset($collection['author']);
unset($collection->license);
Über eine Collection iterieren
Collections sind iterierbar, du kannst sie also in einer foreach-Schleife verwenden:
foreach ($collection as $key => $value) {
echo "$key: $value\n";
}
Elemente zählen
Du kannst die Anzahl der Elemente in einer Collection zählen:
echo count($collection); // Ausgabe: 4
Alle Schlüssel oder Daten abrufen
Alle Schlüssel abrufen:
$keys = $collection->keys(); // ['name', 'version', 'features', 'license']
Alle Daten als Array abrufen:
$data = $collection->getData();
Collection leeren
Alle Elemente entfernen:
$collection->clear();
JSON-Serialisierung
Collections können einfach in JSON konvertiert werden:
echo json_encode($collection);
// Ausgabe: {"name":"FlightPHP","version":3,"features":["routing","views","extending"],"license":"MIT"}
Fortgeschrittene Verwendung
Du kannst das interne Datenarray bei Bedarf vollständig ersetzen:
$collection->setData(['foo' => 'bar']);
Collections sind besonders nützlich, wenn du strukturierte Daten zwischen Komponenten weitergeben möchtest oder wenn du eine objektorientiertere Schnittstelle für Array-Daten bereitstellen willst.
Siehe auch
- Anfragen – Erfahre, wie du HTTP-Anfragen verarbeitest und wie Collections zur Verwaltung von Anfragedaten verwendet werden können.
- SimplePdo – Datenbank-Helfer, der Abfragezeilen als Collections zurückgibt.
Fehlerbehebung
- Wenn du versuchst, auf einen Schlüssel zuzugreifen, der nicht existiert, erhältst du
nullanstatt eines Fehlers. - Denke daran, dass Collections nicht rekursiv sind: Verschachtelte Arrays werden nicht automatisch in Collections umgewandelt.
- Wenn du die Collection zurücksetzen musst, verwende
$collection->clear()oder$collection->setData([]).
Änderungsprotokoll
- v3.0 – Verbesserte Typ-Hinweise und PHP 8+-Unterstützung.
- v1.0 – Erstveröffentlichung der Collection-Klasse.
Learn/flight_vs_fat_free
Flight vs. Fat-Free
Was ist Fat-Free?
Fat-Free (liebevoll F3 genannt) ist ein leistungsstarkes und dennoch einfach zu verwendendes PHP-Mikro-Framework, das dir hilft, dynamische und robuste Webanwendungen schnell zu erstellen!
Flight lässt sich in vielerlei Hinsicht mit Fat-Free vergleichen und ist wahrscheinlich der engste Verwandte in Bezug auf Funktionen und Einfachheit. Fat-Free hat viele Funktionen, die Flight nicht hat, aber es hat auch viele Funktionen, die Flight hat. Fat-Free beginnt, sein Alter zu zeigen und ist nicht mehr so beliebt wie früher.
Updates werden seltener und die Community ist nicht mehr so aktiv wie früher. Der Code ist einfach genug, aber manchmal kann der Mangel an Syntax-Disziplin das Lesen und Verstehen erschweren. Es funktioniert mit PHP 8.3, aber der Code selbst sieht immer noch aus, als stamme er aus PHP 5.3.
Vorteile im Vergleich zu Flight
- Fat-Free hat ein paar mehr Sterne auf GitHub als Flight.
- Fat-Free hat eine ordentliche Dokumentation, aber es mangelt ihr in einigen Bereichen an Klarheit.
- Fat-Free hat einige wenige Ressourcen wie YouTube-Tutorials und Online-Artikel, die zum Erlernen des Frameworks genutzt werden können.
- Fat-Free hat einige hilfreiche Plugins integriert, die manchmal nützlich sind.
- Fat-Free hat ein eingebautes ORM namens Mapper, das zur Interaktion mit deiner Datenbank verwendet werden kann. Flight hat active-record.
- Fat-Free hat Sessions, Caching und Lokalisierung integriert. Flight erfordert die Verwendung von Drittanbieter-Bibliotheken, aber dies wird in der Dokumentation behandelt.
- Fat-Free hat eine kleine Gruppe von Community-erstellten Plugins, die zur Erweiterung des Frameworks verwendet werden können. Flight hat einige in der Dokumentation und auf den Beispielseiten behandelt.
- Fat-Free hat wie Flight keine Abhängigkeiten.
- Fat-Free ist wie Flight darauf ausgerichtet, dem Entwickler die Kontrolle über seine Anwendung zu geben und eine einfache Entwicklererfahrung zu bieten.
- Fat-Free behält wie Flight die Abwärtskompatibilität bei (teilweise weil Updates weniger häufig werden).
- Fat-Free ist wie Flight für Entwickler gedacht, die zum ersten Mal in die Welt der Frameworks eintauchen.
- Fat-Free hat eine eingebaute Template-Engine, die robuster ist als die Template-Engine von Flight. Flight empfiehlt Latte, um dies zu erreichen.
- Fat-Free hat einen einzigartigen CLI-„Route“-Befehl, mit dem du CLI-Anwendungen direkt in Fat-Free erstellen und sie wie eine
GET-Anfrage behandeln kannst. Flight erreicht dies mit runway.
Nachteile im Vergleich zu Flight
- Fat-Free hat einige Implementierungstests und sogar eine eigene, sehr grundlegende Test-Klasse. Es ist jedoch nicht zu 100 % mit Unit-Tests abgedeckt wie Flight.
- Du musst eine Suchmaschine wie Google verwenden, um die Dokumentationsseite tatsächlich zu durchsuchen.
- Flight hat einen Dunkelmodus auf seiner Dokumentationsseite. (Mikrofon-Drop)
- Fat-Free hat einige Module, die hoffnungslos ungepflegt sind.
- Flight hat SimplePdo für den Datenbankzugriff, was etwas einfacher ist als die eingebaute
DB\SQL-Klasse von Fat-Free (und gegenüber dem veralteten PdoWrapper bevorzugt wird). - Flight hat ein Berechtigungs-Plugin, mit dem du deine Anwendung absichern kannst. Fat-Free erfordert die Verwendung einer Drittanbieter-Bibliothek.
- Flight hat ein ORM namens active-record, das sich mehr wie ein ORM anfühlt als der Mapper von Fat-Free. Der zusätzliche Vorteil von
active-recordbesteht darin, dass du Beziehungen zwischen Datensätzen für automatische Joins definieren kannst, während der Mapper von Fat-Free erfordert, dass du SQL-Views erstellst. - Erstaunlicherweise hat Fat-Free keinen Root-Namespace. Flight ist durchgehend namespaced, um nicht mit deinem eigenen Code zu kollidieren. Die
Cache-Klasse ist hier der größte Übeltäter. - Fat-Free hat keine Middleware. Stattdessen gibt es
beforeroute- undafterroute-Hooks, die verwendet werden können, um Anfragen und Antworten in Controllern zu filtern. - Fat-Free kann keine Routen gruppieren.
- Fat-Free hat einen Dependency-Injection-Container-Handler, aber die Dokumentation zur Verwendung ist unglaublich spärlich.
- Das Debuggen kann etwas knifflig werden, da praktisch alles in dem sogenannten
HIVEgespeichert wird.
Learn/extending
Erweitern
Überblick
Flight ist so konzipiert, dass es ein erweiterbares Framework ist. Das Framework kommt mit einer Reihe von Standardmethoden und -komponenten, erlaubt es Ihnen jedoch, Ihre eigenen Methoden zuzuordnen, Ihre eigenen Klassen zu registrieren oder sogar bestehende Klassen und Methoden zu überschreiben.
Verständnis
Es gibt 2 Wege, wie Sie die Funktionalität von Flight erweitern können:
- Methoden zuordnen - Dies wird verwendet, um einfache benutzerdefinierte Methoden zu erstellen, die Sie von überall in Ihrer Anwendung aufrufen können. Diese werden typischerweise für Hilfsfunktionen verwendet, die Sie von überall in Ihrem Code aufrufen möchten.
- Klassen registrieren - Dies wird verwendet, um Ihre eigenen Klassen bei Flight zu registrieren. Dies wird typischerweise für Klassen verwendet, die Abhängigkeiten haben oder Konfiguration erfordern.
Sie können auch bestehende Framework-Methoden überschreiben, um ihr Standardverhalten zu ändern, um den Bedürfnissen Ihres Projekts besser zu entsprechen.
Wenn Sie nach einem DIC (Dependency Injection Container) suchen, schauen Sie auf der Dependency Injection Container-Seite vorbei.
Grundlegende Verwendung
Framework-Methoden überschreiben
Flight erlaubt es Ihnen, seine Standardfunktionalität zu überschreiben, um Ihren eigenen Bedürfnissen zu entsprechen, ohne Code zu modifizieren. Sie können alle überschreibbaren Methoden unten ansehen.
Zum Beispiel ruft Flight, wenn es eine URL nicht einer Route zuordnen kann, die notFound-Methode auf, die eine generische HTTP 404-Antwort sendet. Sie können dieses Verhalten überschreiben, indem Sie die map-Methode verwenden:
Flight::map('notFound', function() {
// Anzeigen einer benutzerdefinierten 404-Seite
include 'errors/404.html';
});
Flight erlaubt es Ihnen auch, Kernkomponenten des Frameworks zu ersetzen. Zum Beispiel können Sie die Standard-Router-Klasse durch Ihre eigene benutzerdefinierte Klasse ersetzen:
// Erstellen Sie Ihre benutzerdefinierte Router-Klasse
class MyRouter extends \flight\net\Router {
// Methoden hier überschreiben
// Zum Beispiel eine Abkürzung für GET-Anfragen, um die
// Pass-Route-Funktion zu entfernen
public function get($pattern, $callback, $alias = '') {
return parent::get($pattern, $callback, false, $alias);
}
}
// Registrieren Sie Ihre benutzerdefinierte Klasse
Flight::register('router', MyRouter::class);
// Wenn Flight die Router-Instanz lädt, wird Ihre Klasse geladen
$myRouter = Flight::router();
$myRouter->get('/hello', function() {
echo "Hello World!";
}, 'hello_alias');
Framework-Methoden wie map und register können jedoch nicht überschrieben werden. Sie erhalten einen Fehler, wenn Sie es versuchen (sehen Sie wieder unten für eine Liste der Methoden).
Zuordbare Framework-Methoden
Das Folgende ist die vollständige Menge der Methoden für das Framework. Es besteht aus Kernmethoden, die reguläre statische Methoden sind, und erweiterbaren Methoden, die zugeordnete Methoden sind, die gefiltert oder überschrieben werden können.
Kernmethoden
Diese Methoden sind zentral für das Framework und können nicht überschrieben werden.
Flight::map(string $name, callable $callback, bool $pass_route = false) // Erstellt eine benutzerdefinierte Framework-Methode.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Registriert eine Klasse für eine Framework-Methode.
Flight::unregister(string $name) // Entregistriert eine Klasse für eine Framework-Methode.
Flight::before(string $name, callable $callback) // Fügt einen Filter vor einer Framework-Methode hinzu.
Flight::after(string $name, callable $callback) // Fügt einen Filter nach einer Framework-Methode hinzu.
Flight::path(string $path) // Fügt einen Pfad für das Autoloading von Klassen hinzu.
Flight::get(string $key) // Holt eine Variable, die von Flight::set() gesetzt wurde.
Flight::set(string $key, mixed $value) // Setzt eine Variable im Flight-Engine.
Flight::has(string $key) // Überprüft, ob eine Variable gesetzt ist.
Flight::clear(array|string $key = []) // Löscht eine Variable.
Flight::init() // Initialisiert das Framework mit seinen Standardeinstellungen.
Flight::app() // Holt die Anwendungsobjekt-Instanz
Flight::request() // Holt die Request-Objekt-Instanz
Flight::response() // Holt die Response-Objekt-Instanz
Flight::router() // Holt die Router-Objekt-Instanz
Flight::view() // Holt die View-Objekt-Instanz
Erweiterbare Methoden
Flight::start() // Startet das Framework.
Flight::stop() // Stoppt das Framework und sendet eine Antwort.
Flight::halt(int $code = 200, string $message = '') // Stoppt das Framework mit einem optionalen Statuscode und einer Nachricht.
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Ordnet ein URL-Muster einem Callback zu.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Ordnet ein POST-Request-URL-Muster einem Callback zu.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Ordnet ein PUT-Request-URL-Muster einem Callback zu.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Ordnet ein PATCH-Request-URL-Muster einem Callback zu.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Ordnet ein DELETE-Request-URL-Muster einem Callback zu.
Flight::group(string $pattern, callable $callback) // Erstellt Gruppierungen für URLs, das Muster muss ein String sein.
Flight::getUrl(string $name, array $params = []) // Generiert eine URL basierend auf einem Route-Alias.
Flight::redirect(string $url, int $code) // Leitet zu einer anderen URL um.
Flight::download(string $filePath) // Lädt eine Datei herunter.
Flight::render(string $file, array $data, ?string $key = null) // Rendert eine Template-Datei.
Flight::error(Throwable $error) // Sendet eine HTTP-500-Antwort.
Flight::notFound() // Sendet eine HTTP-404-Antwort.
Flight::etag(string $id, string $type = 'string') // Führt ETag-HTTP-Caching durch.
Flight::lastModified(int $time) // Führt letztes-Änderungs-HTTP-Caching durch.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Sendet eine JSON-Antwort.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Sendet eine JSONP-Antwort.
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Sendet eine JSON-Antwort und stoppt das Framework.
Flight::onEvent(string $event, callable $callback) // Registriert einen Event-Listener.
Flight::triggerEvent(string $event, ...$args) // Löst ein Event aus.
Jede benutzerdefinierte Methode, die mit map und register hinzugefügt wurde, kann auch gefiltert werden. Für Beispiele, wie man diese Methoden filtert, siehe die Filtering Methods-Anleitung.
Erweiterbare Framework-Klassen
Es gibt mehrere Klassen, deren Funktionalität Sie durch Erweiterung und Registrierung Ihrer eigenen Klasse überschreiben können. Diese Klassen sind:
Flight::app() // Anwendungsklasse - erweitern Sie die flight\Engine-Klasse
Flight::request() // Request-Klasse - erweitern Sie die flight\net\Request-Klasse
Flight::response() // Response-Klasse - erweitern Sie die flight\net\Response-Klasse
Flight::router() // Router-Klasse - erweitern Sie die flight\net\Router-Klasse
Flight::view() // View-Klasse - erweitern Sie die flight\template\View-Klasse
Flight::eventDispatcher() // Event-Dispatcher-Klasse - erweitern Sie die flight\core\Dispatcher-Klasse
Benutzerdefinierte Methoden zuordnen
Um Ihre eigene einfache benutzerdefinierte Methode zuzuordnen, verwenden Sie die map-Funktion:
// Ordnen Sie Ihre Methode zu
Flight::map('hello', function (string $name) {
echo "hello $name!";
});
// Rufen Sie Ihre benutzerdefinierte Methode auf
Flight::hello('Bob');
Während es möglich ist, einfache benutzerdefinierte Methoden zu erstellen, wird empfohlen, einfach Standardfunktionen in PHP zu erstellen. Dies hat Autovervollständigung in IDEs und ist einfacher zu lesen. Das Äquivalent des obigen Codes wäre:
function hello(string $name) {
echo "hello $name!";
}
hello('Bob');
Dies wird mehr verwendet, wenn Sie Variablen in Ihre Methode übergeben müssen, um einen erwarteten Wert zu erhalten. Die Verwendung der register()-Methode wie unten ist mehr für das Übergeben von Konfiguration und dann das Aufrufen Ihrer vorkonfigurierten Klasse.
Benutzerdefinierte Klassen registrieren
Um Ihre eigene Klasse zu registrieren und sie zu konfigurieren, verwenden Sie die register-Funktion. Der Vorteil, den dies gegenüber map() hat, ist, dass Sie dieselbe Klasse wiederverwenden können, wenn Sie diese Funktion aufrufen (wäre hilfreich mit Flight::db(), um dieselbe Instanz zu teilen).
// Registrieren Sie Ihre Klasse
Flight::register('user', User::class);
// Holen Sie eine Instanz Ihrer Klasse
$user = Flight::user();
Die register-Methode erlaubt es Ihnen auch, Parameter an den Konstruktor Ihrer Klasse zu übergeben. Wenn Sie also Ihre benutzerdefinierte Klasse laden, wird sie voreingestellt initialisiert. Sie können die Konstruktor-Parameter definieren, indem Sie ein zusätzliches Array übergeben. Hier ist ein Beispiel für das Laden einer Datenbankverbindung:
// Klasse mit Konstruktor-Parametern registrieren
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);
// Holen Sie eine Instanz Ihrer Klasse
// Dies wird ein Objekt mit den definierten Parametern erstellen
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();
// und wenn Sie es später in Ihrem Code benötigen, rufen Sie einfach dieselbe Methode erneut auf
class SomeController {
public function __construct() {
$this->db = Flight::db();
}
}
Wenn Sie einen zusätzlichen Callback-Parameter übergeben, wird er unmittelbar nach der Klassenkonstruktion ausgeführt. Dies erlaubt es Ihnen, alle Einrichtungsverfahren für Ihr neues Objekt durchzuführen. Die Callback-Funktion nimmt einen Parameter: eine Instanz des neuen Objekts.
// Der Callback wird das konstruierte Objekt übergeben
Flight::register(
'db',
PDO::class,
['mysql:host=localhost;dbname=test', 'user', 'pass'],
function (PDO $db) {
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
);
Standardmäßig erhalten Sie bei jedem Laden Ihrer Klasse eine geteilte Instanz.
Um eine neue Instanz einer Klasse zu erhalten, übergeben Sie einfach false als Parameter:
// Geteilte Instanz der Klasse
$shared = Flight::db();
// Neue Instanz der Klasse
$new = Flight::db(false);
Hinweis: Beachten Sie, dass zugeordnete Methoden Vorrang vor registrierten Klassen haben. Wenn Sie beide mit demselben Namen deklarieren, wird nur die zugeordnete Methode aufgerufen.
Beispiele
Hier sind einige Beispiele, wie Sie Flight mit Funktionalität erweitern können, die nicht im Kern integriert ist.
Logging
Flight hat kein integriertes Logging-System, es ist jedoch wirklich einfach, eine Logging-Bibliothek mit Flight zu verwenden. Hier ist ein Beispiel mit der Monolog-Bibliothek:
// services.php
// Registrieren Sie den Logger bei Flight
Flight::register('log', Monolog\Logger::class, [ 'name' ], function(Monolog\Logger $log) {
$log->pushHandler(new Monolog\Handler\StreamHandler('path/to/your.log', Monolog\Logger::WARNING));
});
Nun, da es registriert ist, können Sie es in Ihrer Anwendung verwenden:
// In Ihrem Controller oder Route
Flight::log()->warning('This is a warning message');
Dies wird eine Nachricht in die von Ihnen angegebene Log-Datei schreiben. Was, wenn Sie etwas protokollieren möchten, wenn ein Fehler auftritt? Sie können die error-Methode verwenden:
// In Ihrem Controller oder Route
Flight::map('error', function(Throwable $ex) {
Flight::log()->error($ex->getMessage());
// Zeigen Sie Ihre benutzerdefinierte Fehlerseite an
include 'errors/500.html';
});
Sie könnten auch ein einfaches APM (Application Performance Monitoring)-System mit den before- und after-Methoden erstellen:
// In Ihrer services.php-Datei
Flight::before('start', function() {
Flight::set('start_time', microtime(true));
});
Flight::after('start', function() {
$end = microtime(true);
$start = Flight::get('start_time');
Flight::log()->info('Request '.Flight::request()->url.' took ' . round($end - $start, 4) . ' seconds');
// Sie könnten auch Ihre Request- oder Response-Header hinzufügen
// um sie zu protokollieren (seien Sie vorsichtig, da dies eine
// Menge Daten sein würde, wenn Sie viele Anfragen haben)
Flight::log()->info('Request Headers: ' . json_encode(Flight::request()->headers));
Flight::log()->info('Response Headers: ' . json_encode(Flight::response()->headers));
});
Caching
Flight hat kein integriertes Caching-System, es ist jedoch wirklich einfach, eine Caching-Bibliothek mit Flight zu verwenden. Hier ist ein Beispiel mit der PHP File Cache-Bibliothek:
// services.php
// Registrieren Sie den Cache bei Flight
Flight::register('cache', \flight\Cache::class, [ __DIR__ . '/../cache/' ], function(\flight\Cache $cache) {
$cache->setDevMode(ENVIRONMENT === 'development');
});
Nun, da es registriert ist, können Sie es in Ihrer Anwendung verwenden:
// In Ihrem Controller oder Route
$data = Flight::cache()->get('my_cache_key');
if (empty($data)) {
// Führen Sie einige Verarbeitung durch, um die Daten zu erhalten
$data = [ 'some' => 'data' ];
Flight::cache()->set('my_cache_key', $data, 3600); // Cache für 1 Stunde
}
Einfache DIC-Objekt-Instantiierung
Wenn Sie einen DIC (Dependency Injection Container) in Ihrer Anwendung verwenden, können Sie Flight verwenden, um Ihnen bei der Instantiierung Ihrer Objekte zu helfen. Hier ist ein Beispiel mit der Dice-Bibliothek:
// services.php
// Erstellen Sie einen neuen Container
$container = new \Dice\Dice;
// Vergessen Sie nicht, ihn sich selbst zuzuweisen wie unten!
$container = $container->addRule('PDO', [
// shared bedeutet, dass dasselbe Objekt jedes Mal zurückgegeben wird
'shared' => true,
'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);
// Nun können wir eine zuordbare Methode erstellen, um jedes Objekt zu erstellen.
Flight::map('make', function($class, $params = []) use ($container) {
return $container->create($class, $params);
});
// Dies registriert den Container-Handler, damit Flight weiß, dass er ihn für Controller/Middleware verwendet
Flight::registerContainerHandler(function($class, $params) {
Flight::make($class, $params);
});
// Sagen wir, wir haben die folgende Beispielklasse, die ein PDO-Objekt im Konstruktor nimmt
class EmailCron {
protected PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function send() {
// Code, der eine E-Mail sendet
}
}
// Und schließlich können Sie Objekte mit Dependency Injection erstellen
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();
Cool, oder?
Siehe auch
- Dependency Injection Container - Wie man einen DIC mit Flight verwendet.
- File Cache - Beispiel für die Verwendung einer Caching-Bibliothek mit Flight.
Fehlerbehebung
- Denken Sie daran, dass zugeordnete Methoden Vorrang vor registrierten Klassen haben. Wenn Sie beide mit demselben Namen deklarieren, wird nur die zugeordnete Methode aufgerufen.
Änderungsprotokoll
- v2.0 - Erste Veröffentlichung.
Learn/json
JSON Wrapper
Übersicht
Die Json-Klasse in Flight bietet eine einfache, konsistente Möglichkeit, JSON-Daten in Ihrer Anwendung zu kodieren und zu dekodieren. Sie umhüllt die nativen JSON-Funktionen von PHP mit besserer Fehlerbehandlung und einigen hilfreichen Standardeinstellungen, was die Arbeit mit JSON einfacher und sicherer macht.
Verständnis
Die Arbeit mit JSON ist in modernen PHP-Apps sehr üblich, insbesondere beim Aufbau von APIs oder der Behandlung von AJAX-Anfragen. Die Json-Klasse zentralisiert alle Ihre JSON-Kodierungen und -Dekodierungen, sodass Sie sich keine Gedanken über seltsame Randfälle oder kryptische Fehler aus den integrierten Funktionen von PHP machen müssen.
Wichtige Funktionen:
- Konsistente Fehlerbehandlung (wirft Ausnahmen bei Fehlern)
- Standardoptionen für Kodierung/Dekodierung (wie unentfesselte Schrägstriche)
- Hilfsmethoden für schöne Ausgabe und Validierung
Grundlegende Verwendung
Daten zu JSON kodieren
Um PHP-Daten in einen JSON-String umzuwandeln, verwenden Sie Json::encode():
use flight\util\Json;
$data = [
'framework' => 'Flight',
'version' => 3,
'features' => ['routing', 'views', 'extending']
];
$json = Json::encode($data);
echo $json;
// Ausgabe: {"framework":"Flight","version":3,"features":["routing","views","extending"]}
Falls die Kodierung fehlschlägt, erhalten Sie eine Ausnahme mit einer hilfreichen Fehlermeldung.
Schöne Ausgabe
Möchten Sie, dass Ihr JSON lesbar für Menschen ist? Verwenden Sie prettyPrint():
echo Json::prettyPrint($data);
/*
{
"framework": "Flight",
"version": 3,
"features": [
"routing",
"views",
"extending"
]
}
*/
JSON-Strings dekodieren
Um einen JSON-String zurück in PHP-Daten umzuwandeln, verwenden Sie Json::decode():
$json = '{"framework":"Flight","version":3}';
$data = Json::decode($json);
echo $data->framework; // Ausgabe: Flight
Wenn Sie ein assoziatives Array anstelle eines Objekts möchten, übergeben Sie true als zweiten Argument:
$data = Json::decode($json, true);
echo $data['framework']; // Ausgabe: Flight
Falls die Dekodierung fehlschlägt, erhalten Sie eine Ausnahme mit einer klaren Fehlermeldung.
JSON validieren
Überprüfen Sie, ob ein String gültiges JSON ist:
if (Json::isValid($json)) {
// Es ist gültig!
} else {
// Kein gültiges JSON
}
Letzten Fehler abrufen
Wenn Sie die letzte JSON-Fehlermeldung überprüfen möchten (aus den nativen PHP-Funktionen):
$error = Json::getLastError();
if ($error !== '') {
echo "Letzter JSON-Fehler: $error";
}
Erweiterte Verwendung
Sie können Kodierungs- und Dekodierungsoptionen anpassen, wenn Sie mehr Kontrolle benötigen (siehe PHP's json_encode-Optionen):
// Kodieren mit HEX_TAG-Option
$json = Json::encode($data, JSON_HEX_TAG);
// Dekodieren mit benutzerdefinierter Tiefe
$data = Json::decode($json, false, 1024);
Siehe auch
- Collections - Für die Arbeit mit strukturierten Daten, die leicht in JSON umgewandelt werden können.
- Configuration - Wie Sie Ihre Flight-App konfigurieren.
- Extending - Wie Sie eigene Hilfsmethoden hinzufügen oder Kernklassen überschreiben.
Fehlerbehebung
- Wenn die Kodierung oder Dekodierung fehlschlägt, wird eine Ausnahme geworfen – umschließen Sie Ihre Aufrufe mit try/catch, wenn Sie Fehler elegant handhaben möchten.
- Wenn Sie unerwartete Ergebnisse erhalten, überprüfen Sie Ihre Daten auf zirkuläre Referenzen oder Nicht-UTF8-Zeichen.
- Verwenden Sie
Json::isValid(), um zu überprüfen, ob ein String gültiges JSON ist, bevor Sie dekodieren.
Änderungsprotokoll
- v3.16.0 - JSON-Wrapper-Hilfsklasse hinzugefügt.
Learn/flight_vs_slim
Flight vs. Slim
Was ist Slim?
Slim ist ein PHP-Micro-Framework, das dir hilft, schnell einfache, aber leistungsstarke Webanwendungen und APIs zu erstellen.
Ein Großteil der Inspiration für einige der v3-Funktionen von Flight stammt tatsächlich von Slim. Das Gruppieren von Routen und das Ausführen von Middleware in einer bestimmten Reihenfolge sind zwei Funktionen, die von Slim inspiriert wurden. Slim v3 wurde mit Fokus auf Einfachheit veröffentlicht, aber es gibt gemischte Bewertungen bezüglich v4.
Vorteile im Vergleich zu Flight
- Slim hat eine größere Community von Entwicklern, die wiederum praktische Module erstellen, die dir helfen, das Rad nicht neu zu erfinden.
- Slim folgt vielen Schnittstellen und Standards, die in der PHP-Community üblich sind, was die Interoperabilität erhöht.
- Slim hat ordentliche Dokumentation und Tutorials, die zum Erlernen des Frameworks genutzt werden können (aber nichts im Vergleich zu Laravel oder Symfony).
- Slim hat verschiedene Ressourcen wie YouTube-Tutorials und Online-Artikel, die zum Erlernen des Frameworks genutzt werden können.
- Slim lässt dich beliebige Komponenten verwenden, um die Kern-Routing-Funktionen zu handhaben, da es PSR-7-konform ist.
Nachteile im Vergleich zu Flight
- Überraschenderweise ist Slim nicht so schnell, wie man es für ein Micro-Framework erwarten würde. Siehe die TechEmpower-Benchmarks für weitere Informationen.
- Flight richtet sich an Entwickler, die eine leichtgewichtige, schnelle und einfach zu bedienende Webanwendung erstellen möchten.
- Flight hat keine Abhängigkeiten, während Slim ein paar Abhängigkeiten hat, die du installieren musst.
- Flight ist auf Einfachheit und Benutzerfreundlichkeit ausgelegt.
- Eine der Kernfunktionen von Flight ist, dass es sein Bestes tut, um Abwärtskompatibilität zu gewährleisten. Slim v3 zu v4 war eine bahnbrechende Änderung.
- Flight ist für Entwickler gedacht, die zum ersten Mal in die Welt der Frameworks eintauchen.
- Flight kann auch Anwendungen auf Unternehmensebene erstellen, hat aber nicht so viele Beispiele und Tutorials wie Slim. Es erfordert auch mehr Disziplin seitens des Entwicklers, um Dinge organisiert und gut strukturiert zu halten.
- Flight gibt dem Entwickler mehr Kontrolle über die Anwendung, während Slim im Hintergrund etwas Magie einschleusen kann.
- Flight hat SimplePdo für den Datenbankzugriff (bevorzugt gegenüber dem veralteten PdoWrapper). Slim erfordert die Verwendung einer Drittanbieter-Bibliothek.
- Flight hat ein Berechtigungs-Plugin, das zum Sichern deiner Anwendung verwendet werden kann. Slim erfordert die Verwendung einer Drittanbieter-Bibliothek.
- Flight hat ein ORM namens active-record, das zur Interaktion mit deiner Datenbank verwendet werden kann. Slim erfordert die Verwendung einer Drittanbieter-Bibliothek.
- Flight hat eine CLI-Anwendung namens runway, mit der du deine Anwendung über die Befehlszeile ausführen kannst. Slim hat das nicht.
Learn/autoloading
Autoloading
Übersicht
Autoloading ist ein Konzept in PHP, bei dem Sie ein Verzeichnis oder mehrere Verzeichnisse angeben, aus denen Klassen geladen werden. Dies ist viel vorteilhafter als require oder include zum Laden von Klassen zu verwenden. Es ist auch eine Voraussetzung für die Verwendung von Composer-Paketen.
Ein korrektes Autoloading ist auch für KI-gestützte Entwicklung wichtig: Agents legen Dateien dort ab, wohin der Namespace zeigt. Wenn die Groß-/Kleinschreibung von Ordnern und Namespaces nicht übereinstimmt, treten unter Linux Fehler wie „Klasse nicht gefunden" auf, selbst wenn die Dinge auf einer Mac-Festplatte ohne Beachtung der Groß-/Kleinschreibung funktioniert haben.
Verständnis
Standardmäßig wird jede Flight-Klasse dank Composer automatisch für Sie geladen. Für Ihre Anwendungsklassen gibt es zwei gängige Ansätze:
- Composer PSR-4 (was das offizielle Grundgerüst verwendet): Ordnen Sie ein Namespace-Präfix einem Verzeichnis in
composer.jsonzu und führen Sie danncomposer dump-autoloadaus. Flight::path(): Weisen Sie den Loader von Flight auf Verzeichnisse hin (praktisch für einfache Apps oder wenn Sie Composer nicht für Anwendungscode verwenden).
Die Verwendung eines Autoloaders vereinfacht Ihren Code erheblich. Anstatt einer Wand von include / require am Anfang jeder Datei werden Klassen geladen, wenn Sie sie zum ersten Mal verwenden.
Groß-/Kleinschreibung (lesen Sie dies zweimal)
Namespaces müssen mit der Verzeichnisstruktur und der Groß-/Kleinschreibung dieser Verzeichnisse übereinstimmen.
| Funktioniert | Schlägt unter Linux fehl |
|---|---|
App\Controller\HomeController → app/Controller/HomeController.php |
App\Controller\… mit Ordner app/controllers/ |
app\controllers\MyController → app/controllers/MyController.php |
Mischung aus App\ mit kleingeschriebenem controllers |
PHP-Namespaces sind in manchen Kontexten case-insensitiv (Groß-/Kleinschreibung wird ignoriert), aber Composer und das Dateisystem sind es nicht. Das offizielle Grundgerüst standardisiert auf:
- Composer:
"App\\": "app/" - Ordner:
Controller,Middleware,Model,Utils(PascalCase), nichtcontrollers/middlewares
Ältere Dokumentationen und Beispiele aus der Community verwendeten manchmal kleingeschriebenes app\controllers. Das funktioniert weiterhin, wenn Ihre Ordner kleingeschrieben sind – aber neue Skeleton-Projekte verwenden App\ + PascalCase-Ordner. Wählen Sie eine Konvention pro Projekt und bleiben Sie dabei, damit Menschen und KI-Tools kein zweites Layout erfinden.
Skeleton (empfohlen für neue Projekte)
Nach composer create-project flightphp/skeleton wird Anwendungscode über Composer automatisch geladen – für App\-Klassen ist kein Flight::path() erforderlich:
{
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
// app/Controller/HomeController.php
namespace App\Controller;
use flight\Engine;
class HomeController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
$this->app->render('welcome', ['message' => 'Hello!']);
}
}
// app/config/routes.php – Dice löst App\Controller\… über den Container auf
$router->get('/', [HomeController::class, 'index']);
Siehe Installation für den vollständigen Verzeichnisbaum und KI & Entwicklererfahrung für die Dokumentation dieses Layouts für Programmierassistenten in AGENTS.md.
Grundlegende Verwendung (Flight::path())
Nehmen wir an, wir haben einen Verzeichnisbaum wie den folgenden:
# Beispielpfad
/home/user/project/my-flight-project/
├── app
│ ├── cache
│ ├── config
│ ├── controllers – enthält die Controller für dieses Projekt
│ ├── translations
│ ├── UTILS – enthält Klassen nur für diese Anwendung (dies ist absichtlich in Großbuchstaben für ein späteres Beispiel)
│ └── views
└── public
└── css
└── js
└── index.php
Ihnen ist vielleicht aufgefallen, dass dies einem typischen App-Verzeichnisbaum ähnelt (die Dokumentationsseite selbst verwendet eine strukturierte Darstellung). Kleingeschriebenes controllers ist hier eine bewusste Wahl – es ist nur nicht die aktuelle Standardeinstellung des Skeletons.
Sie können jedes Verzeichnis zum Laden wie folgt angeben:
/**
* public/index.php
*/
// Fügen Sie einen Pfad zum Autoloader hinzu
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
/**
* app/controllers/MyController.php
*/
// Keine Namespaces erforderlich
// Alle automatisch geladenen Klassen sollten in Pascal Case sein (jedes Wort großgeschrieben, keine Leerzeichen)
class MyController {
public function index() {
// etwas tun
}
}
Namespaces mit Flight::path()
Wenn Sie Namespaces verwenden, wird die Implementierung tatsächlich sehr einfach. Sie sollten die Methode Flight::path() verwenden, um das Wurzelverzeichnis (nicht das Dokumentenwurzelverzeichnis oder den public/-Ordner) Ihrer Anwendung anzugeben.
/**
* public/index.php
*/
// Fügen Sie einen Pfad zum Autoloader hinzu
Flight::path(__DIR__.'/../');
So könnte Ihr Controller nun aussehen. Schauen Sie sich das folgende Beispiel an, aber achten Sie auf die Kommentare mit wichtigen Informationen.
/**
* app/controllers/MyController.php
*/
// Namespaces sind erforderlich
// Namespaces entsprechen der Verzeichnisstruktur
// Namespaces müssen die gleiche Groß-/Kleinschreibung wie die Verzeichnisstruktur verwenden
// Namespaces und Verzeichnisse dürfen keine Unterstriche enthalten (außer wenn Loader::setV2ClassLoading(false) gesetzt ist)
namespace app\controllers;
// Alle automatisch geladenen Klassen sollten in Pascal Case sein (jedes Wort großgeschrieben, keine Leerzeichen)
// Ab 3.7.2 können Sie Pascal_Snake_Case für Ihre Klassennamen verwenden, indem Sie Loader::setV2ClassLoading(false); ausführen.
class MyController {
public function index() {
// etwas tun
}
}
Und wenn Sie eine Klasse in Ihrem utils-Verzeichnis automatisch laden möchten, gehen Sie im Grunde genauso vor:
/**
* app/UTILS/ArrayHelperUtil.php
*/
// Namespace muss mit der Verzeichnisstruktur und der Groß-/Kleinschreibung übereinstimmen (beachten Sie, dass das UTILS-Verzeichnis komplett groß geschrieben ist
// wie im obigen Verzeichnisbaum)
namespace app\UTILS;
class ArrayHelperUtil {
public function changeArrayCase(array $array) {
// etwas tun
}
}
Skeleton-Stil-Namespace (gleiche Regeln, andere Groß-/Kleinschreibung)
/**
* app/Controller/MyController.php
*/
namespace App\Controller;
class MyController {
// ...
}
Die Regel hat sich nicht geändert – nur die vom Skeleton gewählte Groß-/Kleinschreibung von Ordnern und Namespaces. Egal welche Groß-/Kleinschreibung Ihre Ordner verwenden, Ihre namespace-Zeile muss übereinstimmen.
Unterstriche in Klassennamen
Ab 3.7.2 können Sie Pascal_Snake_Case für Ihre Klassennamen verwenden, indem Sie Loader::setV2ClassLoading(false); ausführen. Dadurch können Sie Unterstriche in Ihren Klassennamen verwenden. Dies wird nicht empfohlen, ist aber für diejenigen verfügbar, die es benötigen.
use flight\core\Loader;
/**
* public/index.php
*/
// Fügen Sie einen Pfad zum Autoloader hinzu
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);
/**
* app/controllers/My_Controller.php
*/
// Keine Namespaces erforderlich
class My_Controller {
public function index() {
// etwas tun
}
}
Siehe auch
- Installation – Skeleton-Verzeichnisbaum und
App\-Standardwerte für neue Projekte. - Routing – Wie Sie Routen auf Controller abbilden und Ansichten rendern.
- Dependency Injection – Wie Controller
Engineund Dienste erhalten. - KI & Entwicklererfahrung – Halten Sie Agents mit Ihrem Layout durch
AGENTS.mdim Einklang. - Warum ein Framework? – Vorteile der Verwendung eines Frameworks wie Flight verstehen.
Fehlerbehebung
Wenn Sie nicht herausfinden können, warum Ihre Namespace-Klassen nicht gefunden werden, denken Sie daran: Verwenden Sie mit Flight::path() das Projektwurzelverzeichnis (oder die korrekte Basis für Ihren Namespace), nicht nur einen verschachtelten Ordner, den Sie im Namespace vergessen haben zu spiegeln.
Führen Sie bei Composer PSR-4 nach Änderungen an den composer.json-Zuordnungen composer dump-autoload aus.
Bei Linux-CI oder Produktion ist eine falsche Groß-/Kleinschreibung von Ordnern ein sehr häufiger Fall von „Funktioniert auf meinem Rechner"-Fehlern.
Klasse nicht gefunden (Autoloading funktioniert nicht)
Es kann mehrere Gründe geben, warum dies nicht funktioniert. Im Folgenden finden Sie einige Beispiele.
Falscher Dateiname
Der häufigste Grund ist, dass der Klassenname nicht mit dem Dateinamen übereinstimmt.
Wenn Sie eine Klasse mit dem Namen MyClass haben, sollte die Datei MyClass.php heißen. Wenn Sie eine Klasse mit dem Namen MyClass haben und die Datei myclass.php heißt, kann der Autoloader sie nicht finden.
Falscher Namespace oder falsche Groß-/Kleinschreibung des Ordners
Wenn Sie Namespaces verwenden, sollte der Namespace der Verzeichnisstruktur entsprechen, einschließlich der Groß-/Kleinschreibung.
// ...code...
// wenn sich Ihr MyController in app/Controller (Skeleton) befindet und App\Controller als Namespace hat
// das funktioniert nicht:
Flight::route('/hello', 'MyController->hello');
// Skeleton-Stil:
use App\Controller\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// Älteres Layout mit Kleinbuchstaben (nur wenn Ihre Ordner tatsächlich app/controllers heißen):
use app\controllers\MyController;
Flight::route('/hello', [ MyController::class, 'hello' ]);
// oder voll qualifiziert:
Flight::route('/hello', [ 'App\Controller\MyController', 'hello' ]);
path() nicht definiert (Anwendungscode ohne Composer)
Wenn Sie für Anwendungsklassen auf Flight::path() anstelle von Composer setzen, definieren Sie den Pfad vor Routen, die diese Klassen referenzieren (oft früh im Bootstrap / in public/index.php):
// Einen Pfad zum Autoloader hinzufügen (Projektwurzelverzeichnis für Apps mit Namespaces)
Flight::path(__DIR__.'/../');
Das offizielle Skeleton verwendet hauptsächlich Composer PSR-4 für App\, daher benötigen Sie dort normalerweise kein Flight::path() für Controller und Modelle.
Änderungsprotokoll
- Docs – Dokumentieren von Skeleton
App\+ PascalCase-Ordnern und Fallstricken zur Groß-/Kleinschreibung für Menschen und KI-Tools. - v3.7.2 – Sie können Pascal_Snake_Case für Ihre Klassennamen verwenden, indem Sie
Loader::setV2ClassLoading(false);ausführen. - v2.0 – Autoload-Funktionalität hinzugefügt.
Learn/uploaded_file
Uploaded File Handler
Übersicht
Die UploadedFile-Klasse in Flight erleichtert es, Datei-Uploads in Ihrer Anwendung sicher und einfach zu handhaben. Sie umschließt die Details des PHP-Datei-Upload-Prozesses und bietet Ihnen eine einfache, objektorientierte Möglichkeit, auf Dateiinformationen zuzugreifen und hochgeladene Dateien zu verschieben.
Verständnis
Wenn ein Benutzer eine Datei über ein Formular hochlädt, speichert PHP Informationen über die Datei in der $_FILES-Superglobal. In Flight interagieren Sie selten direkt mit $_FILES. Stattdessen stellt das Request-Objekt von Flight (erreichbar über Flight::request()) eine Methode getUploadedFiles() bereit, die ein Array von UploadedFile-Objekten zurückgibt, was den Datei-Handling viel bequemer und robuster macht.
Die UploadedFile-Klasse bietet Methoden zum:
- Abrufen des ursprünglichen Dateinamens, MIME-Typs, der Größe und des temporären Speicherorts
- Überprüfen auf Upload-Fehler
- Verschieben der hochgeladenen Datei an einen permanenten Speicherort
Diese Klasse hilft Ihnen, gängige Fallstricke bei Datei-Uploads zu vermeiden, wie z. B. das Handhaben von Fehlern oder das sichere Verschieben von Dateien.
Grundlegende Verwendung
Zugriff auf hochgeladene Dateien aus einer Anfrage
Der empfohlene Weg, um auf hochgeladene Dateien zuzugreifen, ist über das Request-Objekt:
Flight::route('POST /upload', function() {
// Für ein Formularfeld namens <input type="file" name="myFile">
$uploadedFiles = Flight::request()->getUploadedFiles();
$file = $uploadedFiles['myFile'];
// Nun können Sie die UploadedFile-Methoden verwenden
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Datei erfolgreich hochgeladen!";
} else {
echo "Upload fehlgeschlagen: " . $file->getError();
}
});
Handhabung mehrerer Datei-Uploads
Wenn Ihr Formular name="myFiles[]" für mehrere Uploads verwendet, erhalten Sie ein Array von UploadedFile-Objekten:
Flight::route('POST /upload', function() {
// Für ein Formularfeld namens <input type="file" name="myFiles[]">
$uploadedFiles = Flight::request()->getUploadedFiles();
foreach ($uploadedFiles['myFiles'] as $file) {
if ($file->getError() === UPLOAD_ERR_OK) {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Hochgeladen: " . $file->getClientFilename() . "<br>";
} else {
echo "Upload fehlgeschlagen: " . $file->getClientFilename() . "<br>";
}
}
});
Manuelles Erstellen einer UploadedFile-Instanz
Normalerweise erstellen Sie keine UploadedFile manuell, aber Sie können es tun, wenn es benötigt wird:
use flight\net\UploadedFile;
$file = new UploadedFile(
$_FILES['myfile']['name'],
$_FILES['myfile']['type'],
$_FILES['myfile']['size'],
$_FILES['myfile']['tmp_name'],
$_FILES['myfile']['error']
);
Zugriff auf Dateiinformationen
Sie können leicht Details über die hochgeladene Datei abrufen:
echo $file->getClientFilename(); // Ursprünglicher Dateiname vom Computer des Benutzers
echo $file->getClientMediaType(); // MIME-Typ (z. B. image/png)
echo $file->getSize(); // Dateigröße in Bytes
echo $file->getTempName(); // Temporärer Dateipfad auf dem Server
echo $file->getError(); // Upload-Fehlercode (0 bedeutet kein Fehler)
Verschieben der hochgeladenen Datei
Nach der Validierung der Datei verschieben Sie sie an einen permanenten Speicherort:
try {
$file->moveTo('/path/to/uploads/' . $file->getClientFilename());
echo "Datei erfolgreich hochgeladen!";
} catch (Exception $e) {
echo "Upload fehlgeschlagen: " . $e->getMessage();
}
Die moveTo()-Methode wirft eine Exception, wenn etwas schiefgeht (wie ein Upload-Fehler oder ein Berechtigungsproblem).
Handhabung von Upload-Fehlern
Wenn es während des Uploads ein Problem gab, können Sie eine lesbare Fehlermeldung abrufen:
if ($file->getError() !== UPLOAD_ERR_OK) {
// Sie können den Fehlercode verwenden oder die Exception von moveTo() abfangen
echo "Es gab einen Fehler beim Hochladen der Datei.";
}
Siehe auch
- Requests - Erfahren Sie, wie Sie auf hochgeladene Dateien aus HTTP-Anfragen zugreifen und sehen Sie weitere Beispiele für Datei-Uploads.
- Configuration - Wie Sie Upload-Limits und Verzeichnisse in PHP konfigurieren.
- Extending - Wie Sie die Kernklassen von Flight anpassen oder erweitern.
Fehlerbehebung
- Überprüfen Sie immer
$file->getError(), bevor Sie die Datei verschieben. - Stellen Sie sicher, dass Ihr Upload-Verzeichnis vom Webserver beschreibbar ist.
- Wenn
moveTo()fehlschlägt, überprüfen Sie die Exception-Nachricht auf Details. - Die PHP-Einstellungen
upload_max_filesizeundpost_max_sizekönnen Datei-Uploads einschränken. - Für mehrere Datei-Uploads iterieren Sie immer durch das Array der
UploadedFile-Objekte.
Changelog
- v3.12.0 -
UploadedFile-Klasse zum Request-Objekt hinzugefügt für einfacheres Datei-Handling.
Guides/unit_testing
Unit-Tests in Flight PHP mit PHPUnit
Diese Anleitung führt in Unit-Tests in Flight PHP mit PHPUnit ein und richtet sich an Anfänger, die verstehen möchten, warum Unit-Tests wichtig sind und wie man sie praktisch anwendet. Wir konzentrieren uns darauf, Verhalten zu testen – sicherzustellen, dass Ihre Anwendung das tut, was Sie erwarten, wie das Senden einer E-Mail oder das Speichern eines Datensatzes – und nicht auf triviale Berechnungen. Wir beginnen mit einem einfachen Route-Handler und gehen zu einem komplexeren Controller über, einschließlich Abhängigkeitsinjektion (DI) und dem Mocken von Diensten von Drittanbietern.
Warum Unit-Tests?
Unit-Tests stellen sicher, dass Ihr Code sich wie erwartet verhält, und fangen Fehler, bevor sie in die Produktion gelangen. Sie sind besonders wertvoll in Flight, wo leichtgewichtiges Routing und Flexibilität zu komplexen Interaktionen führen können. Für Einzelentwickler oder Teams dienen Unit-Tests als Sicherheitsnetz, dokumentieren erwartetes Verhalten und verhindern Regressionen, wenn Sie Code später erneut aufrufen. Sie verbessern auch das Design: Schwer zu testender Code deutet oft auf übermäßig komplexe oder eng gekoppelte Klassen hin.
Im Gegensatz zu vereinfachten Beispielen (z. B. Testen von x * y = z) konzentrieren wir uns auf reale Verhaltensweisen wie das Validieren von Eingaben, das Speichern von Daten oder das Auslösen von Aktionen wie E-Mails. Unser Ziel ist es, Tests zugänglich und aussagekräftig zu machen.
Allgemeine Leitprinzipien
- Verhalten testen, nicht Implementierung: Konzentrieren Sie sich auf Ergebnisse (z. B. „E-Mail gesendet“ oder „Datensatz gespeichert“) statt auf interne Details. Das macht Tests robust gegenüber Refactoring.
- Hören Sie auf,
Flight::zu verwenden: Die statischen Methoden von Flight sind sehr praktisch, erschweren aber das Testen. Sie sollten sich daran gewöhnen, die Variable$appaus$app = Flight::app();zu verwenden.$apphat alle Methoden, die auchFlight::hat. Sie können weiterhin$app->route()oder$this->app->json()in Ihrem Controller usw. verwenden. Sie sollten auch den echten Flight-Router verwenden mit$router = $app->router()und dann$router->get(),$router->post(),$router->group()usw. Siehe Routing. - Halten Sie Tests schnell: Schnelle Tests fördern die häufige Ausführung. Vermeiden Sie langsame Operationen wie Datenbankaufrufe in Unit-Tests. Wenn ein Test langsam ist, ist das ein Zeichen dafür, dass Sie einen Integrationstest schreiben, keinen Unit-Test. Integrationstests sind Tests, bei denen echte Datenbanken, echte HTTP-Aufrufe, echtes E-Mail-Versenden usw. beteiligt sind. Sie haben ihre Berechtigung, aber sie sind langsam und können unbeständig sein, das heißt, sie schlagen manchmal aus unbekannten Gründen fehl.
- Verwenden Sie aussagekräftige Namen: Testnamen sollten das zu testende Verhalten klar beschreiben. Das verbessert Lesbarkeit und Wartbarkeit.
- Vermeiden Sie Globale wie die Pest: Minimieren Sie die Verwendung von
$app->set()und$app->get(), da sie wie globaler Zustand wirken und in jedem Test Mocks erfordern. Bevorzugen Sie DI oder einen DI-Container (siehe Abhängigkeitsinjektions-Container). Auch die Verwendung der Methode$app->map()ist technisch gesehen ein „Global“ und sollte zugunsten von DI vermieden werden. Verwenden Sie eine Session-Bibliothek wie flightphp/session, damit Sie das Session-Objekt in Ihren Tests mocken können. Rufen Sie$_SESSIONnicht direkt in Ihrem Code auf, da dies eine globale Variable in Ihren Code einbringt und das Testen erschwert. - Verwenden Sie Abhängigkeitsinjektion: Injizieren Sie Abhängigkeiten (z. B.
PDO, Mailer) in Controller, um Logik zu isolieren und Mocken zu vereinfachen. Wenn eine Klasse zu viele Abhängigkeiten hat, sollten Sie erwägen, sie in kleinere Klassen zu refaktorisieren, die jeweils gemäß den SOLID-Prinzipien eine einzige Verantwortung haben. - Mocken Sie Dienste von Drittanbietern: Mocken Sie Datenbanken, HTTP-Clients (cURL) oder E-Mail-Dienste, um externe Aufrufe zu vermeiden. Testen Sie ein oder zwei Ebenen tief, aber lassen Sie Ihre Kernlogik laufen. Wenn Ihre App zum Beispiel eine Textnachricht sendet, möchten Sie NICHT wirklich jedes Mal eine Textnachricht senden, wenn Sie Ihre Tests ausführen, da sich diese Kosten summieren (und es langsamer wird). Stattdessen mocken Sie den Textnachrichtendienst und stellen nur sicher, dass Ihr Code den Textnachrichtendienst mit den richtigen Parametern aufgerufen hat.
- Zielen Sie auf hohe Abdeckung, nicht auf Perfektion: 100% Zeilenabdeckung ist gut, aber es bedeutet nicht wirklich, dass alles in Ihrem Code so getestet ist, wie es sein sollte (recherchieren Sie ruhig Zweig-/Pfadabdeckung in PHPUnit). Priorisieren Sie kritisches Verhalten (z. B. Benutzerregistrierung, API-Antworten und das Erfassen fehlgeschlagener Antworten).
- Verwenden Sie Controller für Routen: Verwenden Sie in Ihren Routendefinitionen Controller und keine Closures. Das
flight\Engine $appwird standardmäßig über den Konstruktor in jeden Controller injiziert. Verwenden Sie in Tests$app = new Flight\Engine(), um Flight innerhalb eines Tests zu instanziieren, injizieren Sie es in Ihren Controller und rufen Sie Methoden direkt auf (z. B.$controller->register()). Siehe Flight erweitern und Routing. - Wählen Sie einen Mocking-Stil und bleiben Sie dabei: PHPUnit unterstützt mehrere Mocking-Stile (z. B. Prophecy, integrierte Mocks), oder Sie können anonyme Klassen verwenden, die ihre eigenen Vorteile haben, wie Codevervollständigung, Fehler bei geänderter Methodendefinition usw. Seien Sie einfach konsistent in Ihren Tests. Siehe PHPUnit-Mock-Objekte.
- Verwenden Sie
protected-Sichtbarkeit für Methoden/Eigenschaften, die Sie in Unterklassen testen möchten: Dies ermöglicht es Ihnen, sie in Testunterklassen zu überschreiben, ohne sie öffentlich zu machen. Das ist besonders nützlich für anonyme Klassen-Mocks.
Einrichten von PHPUnit
Richten Sie zuerst PHPUnit in Ihrem Flight-PHP-Projekt mit Composer ein, um einfaches Testen zu ermöglichen. Weitere Details finden Sie im PHPUnit-Getting-Started-Leitfaden.
-
Führen Sie in Ihrem Projektverzeichnis aus:
composer require --dev phpunit/phpunitDies installiert die neueste PHPUnit-Version als Entwicklungsabhängigkeit.
-
Erstellen Sie ein
tests-Verzeichnis im Projektstamm für Testdateien. -
Fügen Sie
composer.jsonein Testskript hinzu, der Einfachheit halber:// weiterer Inhalt von composer.json "scripts": { "test": "phpunit --configuration phpunit.xml" } -
Erstellen Sie eine
phpunit.xml-Datei im Stammverzeichnis:<?xml version="1.0" encoding="UTF-8"?> <phpunit bootstrap="vendor/autoload.php"> <testsuites> <testsuite name="Flight Tests"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
Wenn Ihre Tests erstellt sind, können Sie composer test ausführen, um die Tests auszuführen.
Testen eines einfachen Route-Handlers
Beginnen wir mit einer einfachen Route, die die E-Mail-Eingabe eines Benutzers validiert. Wir testen ihr Verhalten: Für gültige E-Mails wird eine Erfolgsmeldung zurückgegeben, für ungültige ein Fehler. Für die E-Mail-Validierung verwenden wir filter_var.
// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);
// UserController.php
class UserController {
protected $app;
public function __construct(flight\Engine $app) {
$this->app = $app;
}
public function register() {
$email = $this->app->request()->data->email;
$responseArray = [];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$responseArray = ['status' => 'error', 'message' => 'Invalid email'];
} else {
$responseArray = ['status' => 'success', 'message' => 'Valid email'];
}
$this->app->json($responseArray);
}
}
Um dies zu testen, erstellen Sie eine Testdatei. Siehe Unit-Tests und SOLID-Prinzipien für weitere Informationen zur Strukturierung von Tests:
// tests/UserControllerTest.php
use PHPUnit\Framework\TestCase;
use Flight;
use flight\Engine;
class UserControllerTest extends TestCase {
public function testValidEmailReturnsSuccess() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'test@example.com'; // Simuliere POST-Daten
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('success', $output['status']);
$this->assertEquals('Valid email', $output['message']);
}
public function testInvalidEmailReturnsError() {
$app = new Engine();
$request = $app->request();
$request->data->email = 'invalid-email'; // Simuliere POST-Daten
$UserController = new UserController($app);
$UserController->register($request->data->email);
$response = $app->response()->getBody();
$output = json_decode($response, true);
$this->assertEquals('error', $output['status']);
$this->assertEquals('Invalid email', $output['message']);
}
}
Wichtige Punkte:
- Wir simulieren POST-Daten mit der Request-Klasse. Verwenden Sie keine Globals wie
$_POST,$_GETusw., da dies das Testen komplizierter macht (Sie müssen diese Werte immer zurücksetzen, sonst könnten andere Tests fehlschlagen). - Alle Controller erhalten standardmäßig die
flight\Engine-Instanz injiziert, auch ohne dass ein DIC-Container eingerichtet wurde. Das macht es viel einfacher, Controller direkt zu testen. - Es gibt überhaupt keine Verwendung von
Flight::, was den Code leichter testbar macht. - Tests verifizieren das Verhalten: korrekter Status und Meldung für gültige/ungültige E-Mails.
Führen Sie composer test aus, um zu überprüfen, ob die Route wie erwartet funktioniert. Weitere Informationen zu Requests und Responses in Flight finden Sie in der entsprechenden Dokumentation.
Verwenden von Abhängigkeitsinjektion für testbare Controller
Für komplexere Szenarien verwenden Sie Abhängigkeitsinjektion (DI), um Controller testbar zu machen. Vermeiden Sie die Globals von Flight (z. B. Flight::set(), Flight::map(), Flight::register()), da sie wie globaler Zustand wirken und für jeden Test Mocks erfordern. Verwenden Sie stattdessen den DI-Container von Flight, DICE, PHP-DI oder manuelle DI.
Verwenden wir flight\database\SimplePdo anstelle von rohem PDO. Dieser Helfer ist viel einfacher zu mocken und zu testen (und wird gegenüber dem veralteten PdoWrapper bevorzugt).
Hier ist ein Controller, der einen Benutzer in einer Datenbank speichert und eine Willkommens-E-Mail sendet:
use flight\database\SimplePdo;
class UserController {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, SimplePdo $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
// das Hinzufügen des return hier hilft beim Unit-Testen, die Ausführung zu stoppen
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
return $this->app->json(['status' => 'success', 'message' => 'User registered']);
}
}
Wichtige Punkte:
- Der Controller hängt von einer
SimplePdo-Instanz und einemMailerInterface(einem angenommenen E-Mail-Dienst von Drittanbietern) ab. - Abhängigkeiten werden über den Konstruktor injiziert, sodass Globals vermieden werden.
Testen des Controllers mit Mocks
Jetzt testen wir das Verhalten des UserController: Validieren von E-Mails, Speichern in der Datenbank und Senden von E-Mails. Wir mocken die Datenbank und den Mailer, um den Controller zu isolieren.
// tests/UserControllerDICTest.php
use flight\database\SimplePdo;
use PHPUnit\Framework\TestCase;
class UserControllerDICTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
// Manchmal ist das Mischen von Mocking-Stilen notwendig
// Hier verwenden wir den eingebauten Mock von PHPUnit für PDOStatement
$statementMock = $this->createMock(PDOStatement::class);
$statementMock->method('execute')->willReturn(true);
// Verwenden einer anonymen Klasse zum Mocken von SimplePdo
$mockDb = new class($statementMock) extends SimplePdo {
protected $statementMock;
public function __construct($statementMock) {
$this->statementMock = $statementMock;
}
// Wenn wir es auf diese Weise mocken, führen wir keinen echten Datenbankaufruf durch.
// Wir können dies weiter einrichten, um den PDOStatement-Mock zu ändern, um Fehler zu simulieren usw.
public function runQuery(string $sql, array $params = []): PDOStatement {
return $this->statementMock;
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
$this->sentEmail = $email;
return true;
}
};
$app = new Engine();
$app->request()->data->email = 'test@example.com';
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
$this->assertEquals('test@example.com', $mockMailer->sentEmail);
}
public function testInvalidEmailSkipsSaveAndEmail() {
$mockDb = new class() extends SimplePdo {
// Ein leerer Konstruktor umgeht den Elternkonstruktor
public function __construct() {}
public function runQuery(string $sql, array $params = []): PDOStatement {
throw new Exception('Should not be called');
}
};
$mockMailer = new class implements MailerInterface {
public $sentEmail = null;
public function sendWelcome($email): bool {
throw new Exception('Should not be called');
}
};
$app = new Engine();
$app->request()->data->email = 'invalid-email';
// jsonHalt muss gemappt werden, um das Beenden zu vermeiden
$app->map('jsonHalt', function($data) use ($app) {
$app->json($data, 400);
});
$controller = new UserControllerDIC($app, $mockDb, $mockMailer);
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('error', $result['status']);
$this->assertEquals('Invalid email', $result['message']);
}
}
Wichtige Punkte:
- Wir mocken
SimplePdoundMailerInterface, um echte Datenbank- oder E-Mail-Aufrufe zu vermeiden. - Tests verifizieren das Verhalten: Gültige E-Mails lösen Datenbankeinfügungen und E-Mail-Versand aus; ungültige E-Mails überspringen beides.
- Mocken Sie Abhängigkeiten von Drittanbietern (z. B.
SimplePdo,MailerInterface) und lassen Sie die Logik des Controllers laufen.
Zu viel mocken
Seien Sie vorsichtig, nicht zu viel von Ihrem Code zu mocken. Ich gebe Ihnen unten ein Beispiel, warum das schlecht sein kann, unter Verwendung unseres UserController. Wir ändern diese Prüfung in eine Methode namens isEmailValid (unter Verwendung von filter_var) und die anderen neuen Ergänzungen in eine separate Methode namens registerUser.
use flight\database\SimplePdo;
use flight\Engine;
// UserControllerDICV2.php
class UserControllerDICV2 {
protected $app;
protected $db;
protected $mailer;
public function __construct(Engine $app, SimplePdo $db, MailerInterface $mailer) {
$this->app = $app;
$this->db = $db;
$this->mailer = $mailer;
}
public function register() {
$email = $this->app->request()->data->email;
if (!$this->isEmailValid($email)) {
// das Hinzufügen des return hier hilft beim Unit-Testen, die Ausführung zu stoppen
return $this->app->jsonHalt(['status' => 'error', 'message' => 'Invalid email']);
}
$this->registerUser($email);
$this->app->json(['status' => 'success', 'message' => 'User registered']);
}
protected function isEmailValid($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
protected function registerUser($email) {
$this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
$this->mailer->sendWelcome($email);
}
}
Und nun der übermäßig gemockte Unit-Test, der eigentlich nichts testet:
use PHPUnit\Framework\TestCase;
class UserControllerTest extends TestCase {
public function testValidEmailSavesAndSendsEmail() {
$app = new Engine();
$app->request()->data->email = 'test@example.com';
// wir überspringen die zusätzliche Abhängigkeitsinjektion hier, weil es „einfach“ ist
$controller = new class($app) extends UserControllerDICV2 {
protected $app;
// Abhängigkeiten im Konstruktor umgehen
public function __construct($app) {
$this->app = $app;
}
// Wir erzwingen einfach, dass dies gültig ist.
protected function isEmailValid($email) {
return true; // Immer true zurückgeben, echte Validierung umgehen
}
// Die tatsächlichen DB- und Mailer-Aufrufe umgehen
protected function registerUser($email) {
return false;
}
};
$controller->register();
$response = $app->response()->getBody();
$result = json_decode($response, true);
$this->assertEquals('success', $result['status']);
$this->assertEquals('User registered', $result['message']);
}
}
Hurra, wir haben Unit-Tests und sie bestehen! Aber Moment, was, wenn ich tatsächlich die internen Abläufe von isEmailValid oder registerUser ändere? Meine Tests bestehen weiterhin, weil ich die gesamte Funktionalität gemockt habe. Lassen Sie mich zeigen, was ich meine.
// UserControllerDICV2.php
class UserControllerDICV2 {
// ... andere Methoden ...
protected function isEmailValid($email) {
// Geänderte Logik
$validEmail = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
// Jetzt sollte es nur eine bestimmte Domain haben
$validDomain = strpos($email, '@example.com') !== false;
return $validEmail && $validDomain;
}
}
Wenn ich meine obigen Unit-Tests ausführe, bestehen sie immer noch! Aber weil ich nicht auf Verhalten getestet habe (also einen Teil des Codes tatsächlich habe laufen lassen), habe ich möglicherweise einen Fehler programmiert, der in der Produktion nur darauf wartet, aufzutreten. Der Test sollte geändert werden, um das neue Verhalten zu berücksichtigen, und auch das Gegenteil, wenn das Verhalten nicht unseren Erwartungen entspricht.
Vollständiges Beispiel
Ein vollständiges Beispiel eines Flight-PHP-Projekts mit Unit-Tests finden Sie auf GitHub: n0nag0n/flight-unit-tests-guide. Für ein tieferes Verständnis siehe Unit-Tests und SOLID-Prinzipien.
Häufige Fallstricke
- Übermäßiges Mocken: Mocken Sie nicht jede Abhängigkeit; lassen Sie etwas Logik (z. B. Controller-Validierung) laufen, um echtes Verhalten zu testen. Siehe Unit-Tests und SOLID-Prinzipien.
- Globaler Zustand: Die starke Verwendung globaler PHP-Variablen (z. B.
$_SESSION,$_COOKIE) macht Tests brüchig. Gleiches gilt fürFlight::. Refaktorisieren Sie, um Abhängigkeiten explizit zu übergeben. - Komplexe Einrichtung: Wenn die Testeinrichtung umständlich ist, hat Ihre Klasse möglicherweise zu viele Abhängigkeiten oder Verantwortlichkeiten, die gegen die SOLID-Prinzipien verstoßen.
Skalierung mit Unit-Tests
Unit-Tests glänzen in größeren Projekten oder wenn Sie Code nach Monaten erneut aufrufen. Sie dokumentieren Verhalten und fangen Regressionen ab, sodass Sie Ihre App nicht neu lernen müssen. Für Einzelentwickler: Testen Sie kritische Pfade (z. B. Benutzeranmeldung, Zahlungsabwicklung). Für Teams: Tests stellen konsistentes Verhalten über Beiträge hinweg sicher. Siehe Warum Frameworks? für weitere Vorteile von Frameworks und Tests.
Tragen Sie Ihre eigenen Testtipps zum Flight-PHP-Dokumentations-Repository bei!
Geschrieben von n0nag0n 2025
Guides/blog
Erstellen eines einfachen Blogs mit Flight PHP
In diesem Leitfaden erstellen Sie einen einfachen Blog mit dem Flight-PHP-Framework. Sie richten ein Projekt ein, definieren Routen, verwalten Beiträge mit JSON und rendern sie mit der Latte-Template-Engine – alles zeigt die Einfachheit und Flexibilität von Flight. Am Ende haben Sie einen funktionsfähigen Blog mit einer Startseite, einzelnen Beitragsseiten und einem Formular zum Erstellen.
Voraussetzungen
- PHP 7.4+: Auf Ihrem System installiert.
- Composer: Für die Verwaltung von Abhängigkeiten.
- Texteditor: Ein beliebiger Editor wie VS Code oder PHPStorm.
- Grundkenntnisse in PHP und Webentwicklung.
Schritt 1: Projekt einrichten
Beginnen Sie mit der Erstellung eines neuen Projektverzeichnisses und der Installation von Flight über Composer.
-
Verzeichnis erstellen:
mkdir flight-blog cd flight-blog -
Flight installieren:
composer require flightphp/core -
Ein öffentliches Verzeichnis erstellen: Flight verwendet einen einzigen Einstiegspunkt (
index.php). Erstellen Sie einenpublic/-Ordner dafür:mkdir public -
Basis-
index.php: Erstellen Siepublic/index.phpmit einer einfachen „Hallo-Welt“-Route:<?php require '../vendor/autoload.php'; Flight::route('/', function () { echo 'Hello, Flight!'; }); Flight::start(); -
Den eingebauten Server ausführen: Testen Sie Ihre Einrichtung mit dem Entwicklungsserver von PHP:
php -S localhost:8000 -t public/Besuchen Sie
http://localhost:8000, um „Hello, Flight!“ zu sehen.
Schritt 2: Projektstruktur organisieren
Für eine saubere Einrichtung strukturieren Sie Ihr Projekt wie folgt:
flight-blog/
├── app/
│ ├── config/
│ └── views/
├── data/
├── public/
│ └── index.php
├── vendor/
└── composer.json
app/config/: Konfigurationsdateien (z. B. Ereignisse, Routen).app/views/: Vorlagen zum Rendern von Seiten.data/: JSON-Datei zum Speichern von Blogbeiträgen.public/: Web-Root mitindex.php.
Schritt 3: Latte installieren und konfigurieren
Latte ist eine leichtgewichtige Template-Engine, die sich gut in Flight integrieren lässt.
-
Latte installieren:
composer require latte/latte -
Latte in Flight konfigurieren: Aktualisieren Sie
public/index.php, um Latte als View-Engine zu registrieren:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'My Blog']); }); Flight::start(); -
Ein Layout-Template erstellen: In
app/views/layout.latte:<!DOCTYPE html> <html> <head> <title>{$title}</title> </head> <body> <header> <h1>My Blog</h1> <nav> <a href="/">Home</a> | <a href="/create">Create a Post</a> </nav> </header> <main> {block content}{/block} </main> <footer> <p>© {date('Y')} Flight Blog</p> </footer> </body> </html> -
Eine Home-Vorlage erstellen: In
app/views/home.latte:{extends 'layout.latte'} {block content} <h2>{$title}</h2> <ul> {foreach $posts as $post} <li><a href="/post/{$post['slug']}">{$post['title']}</a></li> {/foreach} </ul> {/block}Starten Sie den Server neu, falls Sie ihn beendet haben, und besuchen Sie
http://localhost:8000, um die gerenderte Seite zu sehen. -
Eine Datendatei erstellen: Verwenden Sie eine JSON-Datei, um eine Datenbank der Einfachheit halber zu simulieren. In
data/posts.json:[ { "slug": "first-post", "title": "My First Post", "content": "This is my very first blog post with Flight PHP!" } ]
Schritt 4: Routen definieren
Lagern Sie Ihre Routen für eine bessere Organisation in eine Konfigurationsdatei aus.
-
routes.phperstellen: Inapp/config/routes.php:<?php Flight::route('/', function () { Flight::view()->render('home.latte', ['title' => 'My Blog']); }); Flight::route('/post/@slug', function ($slug) { Flight::view()->render('post.latte', ['title' => 'Post: ' . $slug, 'slug' => $slug]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Create a Post']); }); -
index.phpaktualisieren: Binden Sie die Routendatei ein:<?php require '../vendor/autoload.php'; use Latte\Engine; Flight::register('view', Engine::class, [], function ($latte) { $latte->setTempDirectory(__DIR__ . '/../cache/'); $latte->setLoader(new \Latte\Loaders\FileLoader(__DIR__ . '/../app/views/')); }); require '../app/config/routes.php'; Flight::start();
Schritt 5: Blogbeiträge speichern und abrufen
Fügen Sie die Methoden zum Laden und Speichern von Beiträgen hinzu.
-
Eine Posts-Methode hinzufügen: Fügen Sie in
index.phpeine Methode zum Laden von Beiträgen hinzu:Flight::map('posts', function () { $file = __DIR__ . '/../data/posts.json'; return json_decode(file_get_contents($file), true); }); -
Routen aktualisieren: Ändern Sie
app/config/routes.php, um die Beiträge zu verwenden:<?php Flight::route('/', function () { $posts = Flight::posts(); Flight::view()->render('home.latte', [ 'title' => 'My Blog', 'posts' => $posts ]); }); Flight::route('/post/@slug', function ($slug) { $posts = Flight::posts(); $post = array_filter($posts, fn($p) => $p['slug'] === $slug); $post = reset($post) ?: null; if (!$post) { Flight::notFound(); return; } Flight::view()->render('post.latte', [ 'title' => $post['title'], 'post' => $post ]); }); Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Create a Post']); });
Schritt 6: Vorlagen erstellen
Aktualisieren Sie Ihre Vorlagen, um Beiträge anzuzeigen.
-
Beitragsseite (
app/views/post.latte):{extends 'layout.latte'} {block content} <h2>{$post['title']}</h2> <div class="post-content"> <p>{$post['content']}</p> </div> {/block}
Schritt 7: Beitragserstellung hinzufügen
Behandeln Sie die Formularübermittlung, um neue Beiträge hinzuzufügen.
-
Formular erstellen (
app/views/create.latte):{extends 'layout.latte'} {block content} <h2>{$title}</h2> <form method="POST" action="/create"> <div class="form-group"> <label for="title">Title:</label> <input type="text" name="title" id="title" required> </div> <div class="form-group"> <label for="content">Content:</label> <textarea name="content" id="content" required></textarea> </div> <button type="submit">Save Post</button> </form> {/block} -
POST-Route hinzufügen: In
app/config/routes.php:Flight::route('POST /create', function () { $request = Flight::request(); $title = $request->data['title']; $content = $request->data['content']; $slug = strtolower(str_replace(' ', '-', $title)); $posts = Flight::posts(); $posts[] = ['slug' => $slug, 'title' => $title, 'content' => $content]; file_put_contents(__DIR__ . '/../../data/posts.json', json_encode($posts, JSON_PRETTY_PRINT)); Flight::redirect('/'); }); -
Testen Sie es:
- Besuchen Sie
http://localhost:8000/create. - Senden Sie einen neuen Beitrag (z. B. „Second Post“ mit etwas Inhalt).
- Prüfen Sie die Startseite, um den Beitrag aufgelistet zu sehen.
- Besuchen Sie
Schritt 8: Mit Fehlerbehandlung verbessern
Überschreiben Sie die notFound-Methode für eine bessere 404-Erfahrung.
In index.php:
Flight::map('notFound', function () {
Flight::view()->render('404.latte', ['title' => 'Page Not Found']);
});
Erstellen Sie app/views/404.latte:
{extends 'layout.latte'}
{block content}
<h2>404 - {$title}</h2>
<p>Sorry, that page doesn't exist!</p>
{/block}
Nächste Schritte
- Styling hinzufügen: Verwenden Sie CSS in Ihren Vorlagen für ein besseres Aussehen.
- Datenbank: Ersetzen Sie
posts.jsondurch eine Datenbank wie SQLite mithilfe von SimplePdo. - Validierung: Fügen Sie Prüfungen für doppelte Slugs oder leere Eingaben hinzu.
- Middleware: Implementieren Sie eine Authentifizierung für das Erstellen von Beiträgen.
Fazit
Sie haben einen einfachen Blog mit Flight PHP erstellt! Dieser Leitfaden zeigt Kernfunktionen wie Routing, Templating mit Latte und die Verarbeitung von Formularübermittlungen – und bleibt dabei leichtgewichtig. Entdecken Sie die Dokumentation von Flight für weitere fortgeschrittene Funktionen, um Ihren Blog noch weiter zu bringen!
License
Die MIT-Lizenz (MIT)
Urheberrecht © 2024 @mikecao, @n0nag0n
Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der zugehörigen Dokumentationsdateien (die "Software") erhält, die Erlaubnis erteilt, uneingeschränkt mit der Software zu handeln, einschließlich und ohne Einschränkung der Rechte zur Nutzung, Veränderung, Zusammenführung, Veröffentlichung, Verbreitung, Unterlizenzierung und/oder Verkauf von Kopien der Software, und Personen, denen die Software überlassen wird, zu gestatten, dies zu tun, unter den folgenden Bedingungen:
Der obige Urheberrechtsvermerk und dieser Genehmigungsvermerk sind in allen Kopien oder wesentlichen Teilen der Software enthalten.
DIE SOFTWARE WIRD "WIE BESEHEN" BEREITGESTELLT, OHNE JEGLICHE GARANTIE, AUSDRÜCKLICH ODER IMPLIZIERT, EINSCHLIEßLICH DER GARANTIE DER MARKTFÄHIGKEIT, DER EIGNUNG FÜR EINEN BESTIMMTEN ZWECK UND DER NICHTVERLETZUNG. IN KEINEM FALL HAFTEN DIE AUTOREN ODER COPYRIGHT-INHABER FÜR ANSPRÜCHE, SCHÄDEN ODER ANDERE HAFTUNGEN, OB IN EINER VERTRAGS- ODER DELIKTSKLAGE, DIE AUS ODER IN VERBINDUNG MIT DER SOFTWARE ODER DER VERWENDUNG ODER ANDEREN GESCHÄFTEN MIT DER SOFTWARE ENTSTEHEN.
About
Flight PHP Framework
Flight ist ein schnelles, einfaches und erweiterbares Framework für PHP—entwickelt für Entwickler, die Dinge schnell und ohne Umstände erledigen möchten. Ob Sie eine klassische Webanwendung, eine blitzschnelle API oder eine Kombination mit KI-Coding-Assistenten erstellen, Flights geringe Größe und unkompliziertes Design machen es zur perfekten Wahl. Flight ist schlank ausgelegt, kann aber auch Enterprise-Architekturanforderungen erfüllen.
Warum Flight wählen?
- Anfängerfreundlich: Flight ist ein großartiger Einstieg für neue PHP-Entwickler. Seine klare Struktur und einfache Syntax helfen Ihnen, Webentwicklung zu lernen, ohne in Boilerplate-Code zu versinken.
- Von Profis geliebt: Erfahrene Entwickler schätzen Flight für seine Flexibilität und Kontrolle. Sie können von einem kleinen Prototypen zu einer vollwertigen Anwendung skalieren, ohne Frameworks wechseln zu müssen.
- Rückwärtskompatibel: Wir schätzen Ihre Zeit. Flight v3 ist eine Erweiterung von v2 und behält fast die gesamte gleiche API bei. Wir glauben an Evolution, nicht an Revolution—kein "Weltzerbrechen" mehr bei jeder neuen Hauptversion.
- Keine Abhängigkeiten: Flights Kern ist vollständig frei von Abhängigkeiten—keine Polyfills, keine externen Pakete, nicht einmal PSR-Schnittstellen. Das bedeutet weniger Angriffsvektoren, einen kleineren Fußabdruck und keine überraschenden Breaking Changes durch Upstream-Abhängigkeiten. Optionale Plugins können Abhängigkeiten enthalten, aber der Kern bleibt immer schlank und sicher.
- KI-freundlich: Flights kleine API-Oberfläche und das offizielle Skeleton (ein Layout,
AGENTS.md, Konstruktor-Injektion) erleichtern es KI-Coding-Tools, im Muster zu bleiben. Gleiche Codebasis, ob Sie jede Zeile selbst tippen oder mit einem Agenten zusammenarbeiten. Mehr über die Verwendung von KI mit Flight erfahren.
Video-Übersicht
Schnellstart
Für eine schnelle Minimalinstallation installieren Sie es mit Composer:
composer require flightphp/core
Oder Sie können ein ZIP des Repositories hier herunterladen. Dann haben Sie eine grundlegende index.php-Datei wie die folgende:
<?php
// falls mit Composer installiert
require 'vendor/autoload.php';
// oder falls manuell per ZIP-Datei installiert
// require 'flight/Flight.php';
Flight::route('/', function() {
echo 'hello world!';
});
Flight::route('/json', function() {
Flight::json([
'hello' => 'world'
]);
});
Flight::start();
Das war's! Sie haben eine grundlegende Flight-Anwendung. Sie können diese Datei nun mit php -S localhost:8000 ausführen und http://localhost:8000 in Ihrem Browser aufrufen, um die Ausgabe zu sehen.
Kurze Flight::-Beispiele wie dieses sind großartig zum Lernen und für Micro-Apps. Für ein vollständiges Projektlayout, das Menschen und KI-Tools teilen, verwenden Sie das Skeleton unten.
Skeleton/Boilerplate-App
Es gibt ein offizielles Starter-Template, um Ihnen den Einstieg in jedes neue Flight-Projekt zu erleichtern. Es richtet Struktur, Konfiguration, Composer-Skripte und KI-freundliche Anweisungen von Anfang an ein.
Schauen Sie sich flightphp/skeleton für ein sofort einsetzbares Projekt an oder besuchen Sie die Beispiele-Seite für Inspiration. Möchten Sie Details zum KI-Workflow? Entdecken Sie KI & Developer Experience.
Was Sie erhalten (Übersicht):
App\Namespaces mit PascalCase-Ordnern (app/Controller/,app/Middleware/,app/Model/, …)—die Groß-/Kleinschreibung des Ordners muss mit dem Namespace übereinstimmen (siehe Autoloading)- Dice +
Engine-Injektion, damit Controller testbar bleiben (bevorzugen Sie$this->appstattFlight::im Anwendungscode) - Twig-Views, SimplePdo + ActiveRecord-Beispiel, Runway migrate
- Root
AGENTS.md(plus bereichsspezifische Kopien) undSECURITY.mdfür Assistenten und Sicherheitsrichtlinien
Installation der Skeleton-App
Ganz einfach!
# Erstellen Sie das neue Projekt
composer create-project flightphp/skeleton my-project/
# Wechseln Sie in das neue Projektverzeichnis
cd my-project/
# Starten Sie den lokalen Entwicklungsserver, um sofort loszulegen!
composer start
Es erstellt die Projektstruktur, kopiert config_sample.php → config.php (und .env.example → .env, falls vorhanden), und Sie können loslegen. Optionale Beispieldaten:
php runway migrate
# dann besuchen Sie /posts und /api/posts
Hohe Performance
Flight ist eines der schnellsten PHP-Frameworks. Sein schlankes Kernsystem bedeutet weniger Overhead und mehr Geschwindigkeit—perfekt für traditionelle Anwendungen und moderne, KI-unterstützte Workflows. Alle Benchmarks finden Sie auf TechEmpower
Sehen Sie den Benchmark unten mit einigen anderen beliebten PHP-Frameworks.
| Framework | Plaintext Reqs/sec | JSON Reqs/sec |
|---|---|---|
| Flight | 190,421 | 182,491 |
| Yii | 145,749 | 131,434 |
| Fat-Free | 139,238 | 133,952 |
| Slim | 89,588 | 87,348 |
| Phalcon | 95,911 | 87,675 |
| Symfony | 65,053 | 63,237 |
| Lumen | 40,572 | 39,700 |
| Laravel | 26,657 | 26,901 |
| CodeIgniter | 20,628 | 19,901 |
Flight und KI
Interessiert, wie Flight mit Coding-LLMs zusammenarbeitet? Entdecken Sie, wie AGENTS.md, Runway ai:*-Befehle und das Skeleton-Layout Assistenten auf Kurs halten.
Stabilität und Rückwärtskompatibilität
Wir schätzen Ihre Zeit. Wir haben alle Frameworks gesehen, die sich alle paar Jahre komplett neu erfinden und Entwickler mit kaputtem Code und teuren Migrationen zurücklassen. Flight ist anders. Flight v3 wurde als Erweiterung von v2 konzipiert, was bedeutet, dass die API, die Sie kennen und schätzen, nicht entfernt wurde. Tatsächlich werden die meisten v2-Projekte ohne Änderungen in v3 funktionieren.
Wir sind bestrebt, Flight stabil zu halten, damit Sie sich auf den Aufbau Ihrer Anwendung konzentrieren können, nicht auf die Reparatur Ihres Frameworks. Das Skeleton kann für neue Projekte meinungsstark sein; Kern-APIs bleiben für alle anderen vertraut.
Community
Wir sind auf Matrix Chat
Und Discord
Mitwirken
Es gibt zwei Möglichkeiten, wie Sie zu Flight beitragen können:
- Tragen Sie zum Kern-Framework bei, indem Sie das Core-Repository besuchen.
- Helfen Sie mit, die Dokumentation zu verbessern! Diese Dokumentationswebsite wird auf Github gehostet. Wenn Sie einen Fehler entdecken oder etwas verbessern möchten, können Sie gerne einen Pull Request einreichen. Wir freuen uns über Updates und neue Ideen—besonders rund um KI und neue Technologien!
Systemanforderungen
Flight erfordert PHP 7.4 oder höher.
Hinweis: PHP 7.4 wird unterstützt, weil zum Zeitpunkt der Erstellung dieser Dokumentation (2024) PHP 7.4 die Standardversion für einige LTS-Linux-Distributionen ist. Ein erzwungener Wechsel zu PHP >8 würde bei diesen Nutzern viel Frust verursachen. Das Framework unterstützt auch PHP >8.
Lizenz
Flight wird unter der MIT-Lizenz veröffentlicht.
Awesome-plugins/php_cookie
Cookies
overclokk/cookie ist eine einfache Bibliothek zum Verwalten von Cookies in Ihrer App.
Installation
Die Installation ist mit Composer einfach.
composer require overclokk/cookie
Verwendung
Die Verwendung ist so einfach wie das Registrieren einer neuen Methode in der Flight-Klasse.
use Overclokk\Cookie\Cookie;
/*
* Setzen Sie dies in Ihrer Bootstrap- oder public/index.php-Datei
*/
Flight::register('cookie', Cookie::class);
/**
* ExampleController.php
*/
class ExampleController {
public function login() {
// Setze ein Cookie
// Sie möchten, dass dies falsch ist, damit Sie eine neue Instanz erhalten
// verwenden Sie den folgenden Kommentar, wenn Sie eine Autovervollständigung wünschen
/** @var \Overclokk\Cookie\Cookie $cookie */
$cookie = Flight::cookie(false);
$cookie->set(
'stay_logged_in', // Name des Cookies
'1', // der Wert, den Sie setzen möchten
86400, // Anzahl der Sekunden, die das Cookie dauern soll
'/', // Pfad, auf dem das Cookie verfügbar sein wird
'example.com', // Domain, auf der das Cookie verfügbar sein wird
true, // das Cookie wird nur über eine sichere HTTPS-Verbindung übertragen
true // das Cookie ist nur über das HTTP-Protokoll verfügbar
);
// optional, wenn Sie die Standardwerte beibehalten und eine schnelle Möglichkeit haben möchten, ein Cookie lange zu setzen
$cookie->forever('stay_logged_in', '1');
}
public function home() {
// Überprüfen Sie, ob Sie das Cookie haben
if (Flight::cookie()->has('stay_logged_in')) {
// bringe sie z.B. in den Dashboard-Bereich.
Flight::redirect('/dashboard');
}
}
}Awesome-plugins/php_encryption
PHP-Verschlüsselung
defuse/php-encryption ist eine Bibliothek, die zum Verschlüsseln und Entschlüsseln von Daten verwendet werden kann. Das Einrichten und Starten ist ziemlich einfach, um mit der Verschlüsselung und Entschlüsselung von Daten zu beginnen. Sie haben ein großartiges Tutorial, das dabei hilft, die Grundlagen zur Verwendung der Bibliothek sowie wichtige Sicherheitsaspekte in Bezug auf Verschlüsselung zu erklären.
Installation
Die Installation ist einfach mit Composer.
composer require defuse/php-encryption
Einrichtung
Dann müssen Sie einen Verschlüsselungsschlüssel generieren.
vendor/bin/generate-defuse-key
Das wird einen Schlüssel ausgeben, den Sie sicher aufbewahren müssen. Sie könnten den Schlüssel in Ihrer app/config/config.php-Datei im Array am Ende der Datei aufbewahren. Auch wenn es nicht der perfekte Ort ist, ist es zumindest etwas.
Verwendung
Nun, da Sie die Bibliothek und einen Verschlüsselungsschlüssel haben, können Sie damit beginnen, Daten zu verschlüsseln und zu entschlüsseln.
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
/*
* In Ihrer Bootstrap- oder public/index.php-Datei festlegen
*/
// Verschlüsselungsmethode
Flight::map('encrypt', function($rohdaten) {
$verschlüsselungsschlüssel = /* $config['encryption_key'] oder ein file_get_contents davon, wo Sie den Schlüssel platziert haben */;
return Crypto::encrypt($rohdaten, Key::loadFromAsciiSafeString($verschlüsselungsschlüssel));
});
// Entschlüsselungsmethode
Flight::map('decrypt', function($verschlüsselte_daten) {
$verschlüsselungsschlüssel = /* $config['encryption_key'] oder ein file_get_contents davon, wo Sie den Schlüssel platziert haben */;
try {
$rohdaten = Crypto::decrypt($verschlüsselte_daten, Key::loadFromAsciiSafeString($verschlüsselungsschlüssel));
} catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
// Ein Angriff! Entweder der falsche Schlüssel wurde geladen oder der Geheimtext hat sich seit seiner Erstellung geändert -- entweder in der Datenbank korrupt oder absichtlich von Eve modifiziert, um einen Angriff durchzuführen.
// ... diesen Fall auf eine Art und Weise behandeln, die für Ihre Anwendung geeignet ist ...
}
return $rohdaten;
});
Flight::route('/encrypt', function() {
$verschlüsselte_daten = Flight::encrypt('Das ist ein Geheimnis');
echo $verschlüsselte_daten;
});
Flight::route('/decrypt', function() {
$verschlüsselte_daten = '...'; // Verschlüsselte Daten von irgendwoher erhalten
$entschlüsselte_daten = Flight::decrypt($verschlüsselte_daten);
echo $entschlüsselte_daten;
});Awesome-plugins/php_file_cache
flightphp/cache
Leichte, einfache und eigenständige PHP-Datei-Caching-Klasse, abgeleitet von Wruczek/PHP-File-Cache
Vorteile
- Leicht, eigenständig und einfach
- Gesamter Code in einer Datei - keine unnötigen Treiber.
- Sicher - jede generierte Cache-Datei hat einen PHP-Header mit die, was direkten Zugriff unmöglich macht, selbst wenn jemand den Pfad kennt und Ihr Server nicht richtig konfiguriert ist
- Gut dokumentiert und getestet
- Behandelt Parallelität korrekt über flock
- Unterstützt PHP 7.4+
- Kostenlos unter einer MIT-Lizenz
Diese Dokumentationsseite verwendet diese Bibliothek zum Caching jeder Seite!
Klicken Sie hier, um den Code anzusehen.
Installation
Über Composer installieren:
composer require flightphp/cache
Verwendung
Die Verwendung ist ziemlich einfach. Dies speichert eine Cache-Datei im Cache-Verzeichnis.
use flight\Cache;
$app = Flight::app();
// Sie übergeben das Verzeichnis, in dem der Cache gespeichert wird, an den Konstruktor
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {
// Dies stellt sicher, dass der Cache nur im Produktionsmodus verwendet wird
// ENVIRONMENT ist eine Konstante, die in Ihrer Bootstrap-Datei oder an anderer Stelle in Ihrer App gesetzt wird
$cache->setDevMode(ENVIRONMENT === 'development');
});
Einen Cache-Wert abrufen
Sie verwenden die Methode get(), um einen gecachten Wert abzurufen. Wenn Sie eine praktische Methode möchten, die den Cache aktualisiert, wenn er abgelaufen ist, können Sie refreshIfExpired() verwenden.
// Cache-Instanz abrufen
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
return date("H:i:s"); // Daten zurückgeben, die gecacht werden sollen
}, 10); // 10 Sekunden
// oder
$data = $cache->get('simple-cache-test');
if(empty($data)) {
$data = date("H:i:s");
$cache->set('simple-cache-test', $data, 10); // 10 Sekunden
}
Einen Cache-Wert speichern
Sie verwenden die Methode set(), um einen Wert im Cache zu speichern.
Flight::cache()->set('simple-cache-test', 'my cached data', 10); // 10 Sekunden
Einen Cache-Wert löschen
Sie verwenden die Methode delete(), um einen Wert im Cache zu löschen.
Flight::cache()->delete('simple-cache-test');
Überprüfen, ob ein Cache-Wert existiert
Sie verwenden die Methode exists(), um zu prüfen, ob ein Wert im Cache existiert.
if(Flight::cache()->exists('simple-cache-test')) {
// etwas tun
}
Cache leeren
Sie verwenden die Methode flush(), um den gesamten Cache zu leeren.
Flight::cache()->flush();
Metadaten mit Cache abrufen
Wenn Sie Zeitstempel und andere Metadaten zu einem Cache-Eintrag abrufen möchten, stellen Sie sicher, dass Sie true als korrekten Parameter übergeben.
$data = $cache->refreshIfExpired("simple-cache-meta-test", function () {
echo "Refreshing data!" . PHP_EOL;
return date("H:i:s"); // Daten zurückgeben, die gecacht werden sollen
}, 10, true); // true = mit Metadaten zurückgeben
// oder
$data = $cache->get("simple-cache-meta-test", true); // true = mit Metadaten zurückgeben
/*
Beispiel für ein gecachtes Element, das mit Metadaten abgerufen wurde:
{
"time":1511667506, <-- Unix-Zeitstempel speichern
"expire":10, <-- Ablaufzeit in Sekunden
"data":"04:38:26", <-- deserialisierte Daten
"permanent":false
}
Mit Metadaten können wir beispielsweise berechnen, wann das Element gespeichert wurde oder wann es abläuft
Wir können auch über den Schlüssel "data" auf die Daten selbst zugreifen
*/
$expiresin = ($data["time"] + $data["expire"]) - time(); // Unix-Zeitstempel abrufen, wann die Daten ablaufen, und aktuellen Zeitstempel davon abziehen
$cacheddate = $data["data"]; // wir greifen über den Schlüssel "data" auf die Daten selbst zu
echo "Latest cache save: $cacheddate, expires in $expiresin seconds";
Quellcode
Besuchen Sie https://github.com/flightphp/cache, um den Code anzusehen.
Awesome-plugins/permissions
FlightPHP/Permissions
Dies ist ein Berechtigungsmodul, das in Ihren Projekten verwendet werden kann, wenn Sie mehrere Rollen in Ihrer App haben und jede Rolle eine etwas andere Funktionalität hat. Dieses Modul ermöglicht es Ihnen, Berechtigungen für jede Rolle zu definieren und dann zu überprüfen, ob der aktuelle Benutzer die Berechtigung hat, auf eine bestimmte Seite zuzugreifen oder eine bestimmte Aktion auszuführen.
Klicken Sie hier für das Repository auf GitHub.
Installation
Führen Sie composer require flightphp/permissions aus und schon sind Sie auf dem Weg!
Usage
Zuerst müssen Sie Ihre Berechtigungen einrichten, dann teilen Sie Ihrer App mit, was die Berechtigungen bedeuten. Letztendlich überprüfen Sie Ihre Berechtigungen mit $Permissions->has(), ->can(), oder is(). has() und can() haben die gleiche Funktionalität, sind aber unterschiedlich benannt, um Ihren Code lesbarer zu machen.
Basic Example
Nehmen wir an, Sie haben eine Funktion in Ihrer Anwendung, die überprüft, ob ein Benutzer angemeldet ist. Sie können ein Berechtigungsobjekt wie folgt erstellen:
// index.php
require 'vendor/autoload.php';
// some code
// then you probably have something that tells you who the current role is of the person
// likely you have something where you pull the current role
// from a session variable which defines this
// after someone logs in, otherwise they will have a 'guest' or 'public' role.
$current_role = 'admin';
// setup permissions
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
return $current_role !== 'guest';
});
// You'll probably want to persist this object in Flight somewhere
Flight::set('permission', $permission);
Dann haben Sie irgendwo in einem Controller so etwas.
<?php
// some controller
class SomeController {
public function someAction() {
$permission = Flight::get('permission');
if ($permission->has('loggedIn')) {
// do something
} else {
// do something else
}
}
}
Sie können dies auch verwenden, um zu verfolgen, ob sie die Berechtigung haben, etwas in Ihrer Anwendung zu tun. Wenn Sie beispielsweise eine Möglichkeit haben, dass Benutzer mit dem Posten in Ihrer Software interagieren können, können Sie überprüfen, ob sie die Berechtigung haben, bestimmte Aktionen auszuführen.
$current_role = 'admin';
// setup permissions
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
if($current_role === 'admin') {
$permissions = ['create', 'read', 'update', 'delete'];
} else if($current_role === 'editor') {
$permissions = ['create', 'read', 'update'];
} else if($current_role === 'author') {
$permissions = ['create', 'read'];
} else if($current_role === 'contributor') {
$permissions = ['create'];
} else {
$permissions = [];
}
return $permissions;
});
Flight::set('permission', $permission);
Dann irgendwo in einem Controller...
class PostController {
public function create() {
$permission = Flight::get('permission');
if ($permission->can('post.create')) {
// do something
} else {
// do something else
}
}
}
Injecting dependencies
Sie können Abhängigkeiten in die Closure injizieren, die die Berechtigungen definiert. Dies ist nützlich, wenn Sie eine Art Umschalter, ID oder einen anderen Datenpunkt haben, gegen den Sie überprüfen möchten. Das Gleiche funktioniert auch für Class->Method-Aufrufe, außer dass Sie die Argumente in der Methode definieren.
Closures
$Permission->defineRule('order', function(string $current_role, MyDependency $MyDependency = null) {
// ... code
});
// in your controller file
public function createOrder() {
$MyDependency = Flight::myDependency();
$permission = Flight::get('permission');
if ($permission->can('order.create', $MyDependency)) {
// do something
} else {
// do something else
}
}
Classes
namespace MyApp;
class Permissions {
public function order(string $current_role, MyDependency $MyDependency = null) {
// ... code
}
}
Shortcut to set permissions with classes
Sie können auch Klassen verwenden, um Ihre Berechtigungen zu definieren. Dies ist nützlich, wenn Sie viele Berechtigungen haben und Ihren Code sauber halten möchten. Sie können so etwas tun:
<?php
// bootstrap code
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');
// myapp/Permissions.php
namespace MyApp;
class Permissions {
public function order(string $current_role, int $user_id) {
// Assuming you set this up beforehand
/** @var \flight\database\SimplePdo $db */
$db = Flight::db();
$allowed_permissions = [ 'read' ]; // everyone can view an order
if($current_role === 'manager') {
$allowed_permissions[] = 'create'; // managers can create orders
}
$some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
if($some_special_toggle_from_db) {
$allowed_permissions[] = 'update'; // if the user has a special toggle, they can update orders
}
if($current_role === 'admin') {
$allowed_permissions[] = 'delete'; // admins can delete orders
}
return $allowed_permissions;
}
}
Das Coole daran ist, dass es auch eine Abkürzung gibt, die Sie verwenden können (die auch zwischengespeichert werden kann!!!), bei der Sie der Berechtigungsklasse einfach sagen, alle Methoden in einer Klasse in Berechtigungen zu mappen. Wenn Sie also eine Methode namens order() und eine Methode namens company() haben, werden diese automatisch gemappt, sodass Sie einfach $Permissions->has('order.read') oder $Permissions->has('company.read') ausführen können und es funktioniert. Das Definieren ist sehr schwierig, also bleiben Sie bei mir hier. Sie müssen nur Folgendes tun:
Erstellen Sie die Klasse von Berechtigungen, die Sie gruppieren möchten.
class MyPermissions {
public function order(string $current_role, int $order_id = 0): array {
// code to determine permissions
return $permissions_array;
}
public function company(string $current_role, int $company_id): array {
// code to determine permissions
return $permissions_array;
}
}
Dann machen Sie die Berechtigungen mit dieser Bibliothek erkennbar.
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);
Schließlich rufen Sie die Berechtigung in Ihrer Codebasis auf, um zu überprüfen, ob der Benutzer berechtigt ist, eine bestimmte Berechtigung auszuführen.
class SomeController {
public function createOrder() {
if(Flight::get('permissions')->can('order.create') === false) {
die('You can\'t create an order. Sorry!');
}
}
}
Caching
Um das Caching zu aktivieren, sehen Sie sich die einfache wruczak/phpfilecache Bibliothek an. Ein Beispiel für die Aktivierung finden Sie unten.
// this $app can be part of your code, or
// you can just pass null and it will
// pull from Flight::app() in the constructor
$app = Flight::app();
// For now it accepts this as a file cache. Others can easily
// be added in the future.
$Cache = new Wruczek\PhpFileCache\PhpFileCache;
$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 is how many seconds to cache this for. Leave this off to not use caching
Und schon geht's los!
Awesome-plugins/simple_job_queue
Einfache Job-Warteschlange
Die einfache Job-Warteschlange ist eine Bibliothek, die verwendet werden kann, um Jobs asynchron zu verarbeiten. Sie kann mit beanstalkd, MySQL/MariaDB, SQLite und PostgreSQL verwendet werden.
Installation
composer require n0nag0n/simple-job-queue
Verwendung
Damit dies funktioniert, benötigen Sie eine Möglichkeit, Jobs zur Warteschlange hinzuzufügen, und eine Möglichkeit, die Jobs zu verarbeiten (einen Worker). Im Folgenden finden Sie Beispiele, wie man einen Job zur Warteschlange hinzufügt und wie man den Job verarbeitet.
Hinzufügen zu Flight
Das Hinzufügen dieses Codes zu Flight ist einfach und erfolgt mit der Methode register(). Unten finden Sie ein Beispiel, wie Sie dies zu Flight hinzufügen.
<?php
require 'vendor/autoload.php';
// Ändern Sie ['mysql'] in ['beanstalkd'], wenn Sie beanstalkd verwenden möchten
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
// Wenn Sie bereits eine PDO-Verbindung zu Flight::db() haben;
$Job_Queue->addQueueConnection(Flight::db());
// Oder wenn Sie beanstalkd/Pheanstalk verwenden
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
});
Einen neuen Job hinzufügen
Wenn Sie einen Job hinzufügen, müssen Sie eine Pipeline (Warteschlange) angeben. Dies ist vergleichbar mit einem Kanal in RabbitMQ oder einem Tube in beanstalkd.
<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));
Einen Worker ausführen
Hier ist eine Beispieldatei, wie man einen Worker ausführt.
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// PDO-Verbindung
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);
// Oder wenn Sie beanstalkd/Pheanstalk verwenden
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);
$Job_Queue->watchPipeline('send_important_emails');
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
// Passen Sie es an, was Ihnen nachts besser schlafen lässt (nur für Datenbankwarteschlangen, beanstalkd benötigt diese if-Anweisung nicht)
if(empty($job)) {
usleep(500000);
continue;
}
echo "Verarbeite {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
} else {
// Dies entfernt es aus der bereitstehenden Warteschlange und legt es in eine andere Warteschlange, die später aufgegriffen und "getreten" werden kann.
$Job_Queue->buryJob($job);
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
}
}
Lange Prozesse mit Supervisord verwalten
Supervisord ist ein Prozesskontrollsystem, das sicherstellt, dass Ihre Worker-Prozesse kontinuierlich laufen. Hier ist eine umfassendere Anleitung, wie Sie es mit Ihrem einfachen Job-Queue-Worker einrichten:
Supervisord installieren
# Auf Ubuntu/Debian
sudo apt-get install supervisor
# Auf CentOS/RHEL
sudo yum install supervisor
# Auf macOS mit Homebrew
brew install supervisor
Erstellen eines Worker-Skripts
Zuerst speichern Sie Ihren Worker-Code in einer dedizierten PHP-Datei:
<?php
require 'vendor/autoload.php';
$Job_Queue = new n0nag0n\Job_Queue('mysql');
// PDO-Verbindung
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);
// Setzen Sie die Pipeline, die überwacht werden soll
$Job_Queue->watchPipeline('send_important_emails');
// Protokolliere den Start des Workers
echo date('Y-m-d H:i:s') . " - Worker gestartet\n";
while(true) {
$job = $Job_Queue->getNextJobAndReserve();
if(empty($job)) {
usleep(500000); // Schlafen für 0,5 Sekunden
continue;
}
echo date('Y-m-d H:i:s') . " - Verarbeite Job {$job['id']}\n";
$payload = json_decode($job['payload'], true);
try {
$result = doSomethingThatDoesSomething($payload);
if($result === true) {
$Job_Queue->deleteJob($job);
echo date('Y-m-d H:i:s') . " - Job {$job['id']} erfolgreich abgeschlossen\n";
} else {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Job {$job['id']} fehlgeschlagen, beerdigt\n";
}
} catch(Exception $e) {
$Job_Queue->buryJob($job);
echo date('Y-m-d H:i:s') . " - Ausnahme bei der Verarbeitung des Jobs {$job['id']}: {$e->getMessage()}\n";
}
}
Konfigurieren von Supervisord
Erstellen Sie eine Konfigurationsdatei für Ihren Worker:
[program:email_worker]
command=php /path/to/worker.php
directory=/path/to/project
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/simple_job_queue_err.log
stdout_logfile=/var/log/simple_job_queue.log
user=www-data
numprocs=2
process_name=%(program_name)s_%(process_num)02d
Wichtige Konfigurationsoptionen:
command: Der Befehl zum Ausführen Ihres Workersdirectory: Arbeitsverzeichnis für den Workerautostart: Automatisch starten, wenn supervisord startetautorestart: Automatisch neu starten, wenn der Prozess beendet wirdstartretries: Anzahl der Versuche, den Start zu wiederholen, wenn er fehlschlägtstderr_logfile/stdout_logfile: Speicherorte der Protokolldateienuser: Systembenutzer, unter dem der Prozess ausgeführt werden sollnumprocs: Anzahl der auszuführenden Worker-Instanzenprocess_name: Namensformat für mehrere Worker-Prozesse
Worker mit Supervisorctl verwalten
Nach dem Erstellen oder Modifizieren der Konfiguration:
# Supervisor-Konfiguration neu laden
sudo supervisorctl reread
sudo supervisorctl update
# Steuerung spezifischer Worker-Prozesse
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*
Mehrere Pipelines ausführen
Für mehrere Pipelines erstellen Sie separate Worker-Dateien und Konfigurationen:
[program:email_worker]
command=php /path/to/email_worker.php
# ... andere Konfigurationen ...
[program:notification_worker]
command=php /path/to/notification_worker.php
# ... andere Konfigurationen ...
Überwachen und Protokolle
Überprüfen Sie die Protokolle, um die Aktivität der Worker zu überwachen:
# Protokolle anzeigen
sudo tail -f /var/log/simple_job_queue.log
# Status überprüfen
sudo supervisorctl status
Dieses Setup sorgt dafür, dass Ihre Job-Worker auch nach Abstürzen, Serverneustarts oder anderen Problemen weiterlaufen, was Ihr Warteschlangensystem zuverlässig für Produktionsumgebungen macht.
Awesome-plugins/jwt
Firebase JWT - JSON Web Token Authentifizierung
JWT (JSON Web Tokens) sind eine kompakte, URL-sichere Methode, um Ansprüche zwischen Ihrer Anwendung und einem Client darzustellen. Sie eignen sich perfekt für zustandslose API-Authentifizierung – kein Bedarf an serverseitiger Sitzungsspeicherung! Diese Anleitung zeigt Ihnen, wie Sie Firebase JWT mit Flight für sichere, tokenbasierte Authentifizierung integrieren.
Besuchen Sie das Github-Repository für die vollständige Dokumentation und Details.
Was ist JWT?
Ein JSON Web Token ist eine Zeichenkette, die aus drei Teilen besteht:
- Header: Metadaten über den Token (Algorithmus, Typ)
- Payload: Ihre Daten (Benutzer-ID, Rollen, Ablaufdatum usw.)
- Signature: Kryptografische Signatur zur Überprüfung der Authentizität
Beispiel-JWT: eyJ0eXAiOiJKV1QiLCJhbGc... (sieht aus wie Kauderwelsch, ist aber strukturierte Daten!)
Warum JWT verwenden?
- Zustandslos: Keine serverseitige Sitzungsspeicherung erforderlich – perfekt für Microservices und APIs
- Skalierbar: Funktioniert hervorragend mit Load Balancern, da keine Sitzungsaffinität erforderlich ist
- Cross-Domain: Kann über verschiedene Domains und Dienste hinweg verwendet werden
- Mobile-freundlich: Ideal für Mobile-Apps, wo Cookies möglicherweise nicht gut funktionieren
- Standardisiert: Branchenstandard-Ansatz (RFC 7519)
Installation
Installieren Sie es über Composer:
composer require firebase/php-jwt
Grundlegende Verwendung
Hier ist ein schnelles Beispiel zum Erstellen und Überprüfen eines JWT:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// Ihr geheimer Schlüssel (HALTEN SIE DAS SICHER!)
$secretKey = 'your-256-bit-secret-key-here-keep-it-safe';
// Einen Token erstellen
$payload = [
'user_id' => 123,
'username' => 'johndoe',
'role' => 'admin',
'iat' => time(), // Issued at
'exp' => time() + 3600 // Läuft in 1 Stunde ab
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
echo "Token: " . $jwt;
// Einen Token überprüfen und dekodieren
try {
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
echo "User ID: " . $decoded->user_id;
} catch (Exception $e) {
echo "Ungültiger Token: " . $e->getMessage();
}
JWT-Middleware für Flight (Empfohlener Ansatz)
Die gängigste und nützlichste Methode, JWT mit Flight zu verwenden, ist als Middleware, um Ihre API-Routen zu schützen. Hier ist ein vollständiges, produktionsreifes Beispiel:
Schritt 1: Erstellen einer JWT-Middleware-Klasse
// app/middleware/JwtMiddleware.php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use flight\Engine;
class JwtMiddleware {
protected Engine $app;
protected string $secretKey;
public function __construct(Engine $app) {
$this->app = $app;
// Speichern Sie Ihren geheimen Schlüssel in app/config/config.php, NICHT hartcodiert!
$this->secretKey = $app->get('config')['jwt_secret'];
}
public function before(array $params) {
$authHeader = $this->app->request()->getHeader('Authorization');
// Überprüfen, ob der Authorization-Header existiert
if (empty($authHeader)) {
$this->app->jsonHalt(['error' => 'Kein Autorisierungstoken bereitgestellt'], 401);
}
// Token aus dem "Bearer <token>"-Format extrahieren
if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
$this->app->jsonHalt(['error' => 'Ungültiges Autorisierungsformat. Verwenden Sie: Bearer <token>'], 401);
}
$jwt = $matches[1];
try {
// Den Token dekodieren und überprüfen
$decoded = JWT::decode($jwt, new Key($this->secretKey, 'HS256'));
// Benutzerdaten in der Anfrage speichern, um sie in Routen-Handlern zu verwenden
$this->app->request()->data->user = $decoded;
} catch (ExpiredException $e) {
$this->app->jsonHalt(['error' => 'Token ist abgelaufen'], 401);
} catch (SignatureInvalidException $e) {
$this->app->jsonHalt(['error' => 'Ungültige Token-Signatur'], 401);
} catch (Exception $e) {
$this->app->jsonHalt(['error' => 'Ungültiger Token: ' . $e->getMessage()], 401);
}
}
}
Schritt 2: JWT-Geheimschlüssel in Ihrer Konfiguration registrieren
// app/config/config.php
return [
'jwt_secret' => getenv('JWT_SECRET') ?: 'your-fallback-secret-for-development'
];
// app/config/bootstrap.php oder index.php
// Stellen Sie sicher, dass Sie diese Zeile hinzufügen, wenn Sie die Konfiguration der App zugänglich machen möchten
$app->set('config', $config);
Sicherheitshinweis: Codieren Sie Ihren geheimen Schlüssel niemals hart! Verwenden Sie Umgebungsvariablen in der Produktion.
Schritt 3: Ihre Routen mit Middleware schützen
// Eine einzelne Route schützen
Flight::route('GET /api/user/profile', function() {
$user = Flight::request()->data->user; // Von der Middleware gesetzt
Flight::json([
'user_id' => $user->user_id,
'username' => $user->username,
'role' => $user->role
]);
})->addMiddleware(JwtMiddleware::class);
// Eine gesamte Gruppe von Routen schützen (häufiger!)
Flight::group('/api', function() {
Flight::route('GET /users', function() { /* ... */ });
Flight::route('GET /posts', function() { /* ... */ });
Flight::route('POST /posts', function() { /* ... */ });
Flight::route('DELETE /posts/@id', function($id) { /* ... */ });
}, [ JwtMiddleware::class ]); // Alle Routen in dieser Gruppe sind geschützt!
Für weitere Details zu Middleware siehe die Middleware-Dokumentation.
Häufige Anwendungsfälle
1. Login-Endpunkt (Token-Generierung)
Erstellen Sie eine Route, die nach erfolgreicher Authentifizierung ein JWT generiert:
Flight::route('POST /api/login', function() {
$data = Flight::request()->data;
$username = $data->username ?? '';
$password = $data->password ?? '';
// Anmeldeinformationen validieren (Beispiel – verwenden Sie Ihre eigene Logik!)
$user = validateUserCredentials($username, $password);
if (!$user) {
Flight::jsonHalt(['error' => 'Ungültige Anmeldeinformationen'], 401);
}
// JWT generieren
$secretKey = Flight::get('config')['jwt_secret'];
$payload = [
'user_id' => $user->id,
'username' => $user->username,
'role' => $user->role,
'iat' => time(),
'exp' => time() + (60 * 60) // 1 Stunde Ablauf
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
Flight::json([
'success' => true,
'token' => $jwt,
'expires_in' => 3600
]);
});
function validateUserCredentials($username, $password) {
// Ihre Datenbankabfrage und Passwortüberprüfung hier
// Beispiel:
$db = Flight::db();
$user = $db->fetchRow("SELECT * FROM users WHERE username = ?", [$username]);
if ($user && password_verify($password, $user['password_hash'])) {
return (object) [
'id' => $user['id'],
'username' => $user['username'],
'role' => $user['role']
];
}
return null;
}
2. Token-Refresh-Flow
Implementieren Sie ein Refresh-Token-System für langfristige Sitzungen:
Flight::route('POST /api/login', function() {
// ... Anmeldeinformationen validieren ...
$secretKey = Flight::get('config')['jwt_secret'];
$refreshSecret = Flight::get('config')['jwt_refresh_secret'];
// Kurzlebiges Access-Token (15 Minuten)
$accessToken = JWT::encode([
'user_id' => $user->id,
'type' => 'access',
'iat' => time(),
'exp' => time() + (15 * 60)
], $secretKey, 'HS256');
// Langlebiges Refresh-Token (7 Tage)
$refreshToken = JWT::encode([
'user_id' => $user->id,
'type' => 'refresh',
'iat' => time(),
'exp' => time() + (7 * 24 * 60 * 60)
], $refreshSecret, 'HS256');
Flight::json([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => 900
]);
});
Flight::route('POST /api/refresh', function() {
$refreshToken = Flight::request()->data->refresh_token ?? '';
$refreshSecret = Flight::get('config')['jwt_refresh_secret'];
try {
$decoded = JWT::decode($refreshToken, new Key($refreshSecret, 'HS256'));
// Überprüfen, ob es sich um ein Refresh-Token handelt
if ($decoded->type !== 'refresh') {
Flight::jsonHalt(['error' => 'Ungültiger Token-Typ'], 401);
}
// Neues Access-Token generieren
$secretKey = Flight::get('config')['jwt_secret'];
$accessToken = JWT::encode([
'user_id' => $decoded->user_id,
'type' => 'access',
'iat' => time(),
'exp' => time() + (15 * 60)
], $secretKey, 'HS256');
Flight::json([
'access_token' => $accessToken,
'expires_in' => 900
]);
} catch (Exception $e) {
Flight::jsonHalt(['error' => 'Ungültiges Refresh-Token'], 401);
}
});
3. Rollenbasierte Zugriffssteuerung
Erweitern Sie Ihre Middleware, um Benutzerrollen zu überprüfen:
class JwtRoleMiddleware {
protected Engine $app;
protected array $allowedRoles;
public function __construct(Engine $app, array $allowedRoles = []) {
$this->app = $app;
$this->allowedRoles = $allowedRoles;
}
public function before(array $params) {
// Angenommen, JwtMiddleware hat bereits ausgeführt und Benutzerdaten gesetzt
$user = $this->app->request()->data->user ?? null;
if (!$user) {
$this->app->jsonHalt(['error' => 'Authentifizierung erforderlich'], 401);
}
// Überprüfen, ob der Benutzer die erforderliche Rolle hat
if (!empty($this->allowedRoles) && !in_array($user->role, $this->allowedRoles)) {
$this->app->jsonHalt(['error' => 'Unzureichende Berechtigungen'], 403);
}
}
}
// Verwendung: Nur-Admin-Route
Flight::route('DELETE /api/users/@id', function($id) {
// Benutzer löschen Logik
})->addMiddleware([
JwtMiddleware::class,
new JwtRoleMiddleware(Flight::app(), ['admin'])
]);
4. Öffentliche API mit Rate Limiting pro Benutzer
Verwenden Sie JWT, um Benutzer ohne Sitzungen zu verfolgen und zu rate-limitieren:
class RateLimitMiddleware {
public function before(array $params) {
$user = Flight::request()->data->user ?? null;
$userId = $user ? $user->user_id : Flight::request()->ip;
$cacheKey = "rate_limit:$userId";
// Stellen Sie sicher, dass Sie einen Cache-Dienst in app/config/services.php eingerichtet haben
$requests = Flight::cache()->get($cacheKey, 0);
if ($requests >= 100) { // 100 Anfragen pro Stunde
Flight::jsonHalt(['error' => 'Rate Limit überschritten'], 429);
}
Flight::cache()->set($cacheKey, $requests + 1, 3600);
}
}
Best Practices für Sicherheit
1. Starke geheime Schlüssel verwenden
// Einen sicheren geheimen Schlüssel generieren (einmal ausführen, in .env-Datei speichern)
$secretKey = base64_encode(random_bytes(32));
echo $secretKey; // Speichern Sie das in Ihrer .env-Datei!
2. Geheimnisse in Umgebungsvariablen speichern
// Commiten Sie Geheimnisse niemals in die Versionskontrolle!
// Verwenden Sie eine .env-Datei und eine Bibliothek wie vlucas/phpdotenv
// .env-Datei:
// JWT_SECRET=your-base64-encoded-secret-here
// JWT_REFRESH_SECRET=another-base64-encoded-secret-here
// Sie können auch die app/config/config.php-Datei verwenden, um Ihre Geheimnisse zu speichern
// stellen Sie nur sicher, dass die Konfigurationsdatei nicht in die Versionskontrolle committed wird
// return [
// 'jwt_secret' => 'your-base64-encoded-secret-here',
// 'jwt_refresh_secret' => 'another-base64-encoded-secret-here',
// ];
// In Ihrer App:
$secretKey = getenv('JWT_SECRET');
3. Geeignete Ablaufzeiten festlegen
// Gute Praxis: Kurzlebige Access-Tokens
'exp' => time() + (15 * 60) // 15 Minuten
// Für Refresh-Tokens: Längere Ablaufzeit
'exp' => time() + (7 * 24 * 60 * 60) // 7 Tage
4. HTTPS in der Produktion verwenden
JWTs sollten immer über HTTPS übertragen werden. Senden Sie Tokens in der Produktion niemals über einfaches HTTP!
5. Token-Ansprüche validieren
Validieren Sie immer die Ansprüche, die Sie interessieren:
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
// Überprüfung des Ablaufs wird automatisch von der Bibliothek gehandhabt
// Aber Sie können benutzerdefinierte Validierungen hinzufügen:
if ($decoded->iat > time()) {
throw new Exception('Token vor Ausstellung verwendet');
}
if (isset($decoded->nbf) && $decoded->nbf > time()) {
throw new Exception('Token noch nicht gültig');
}
6. Token-Blacklisting für Logout in Betracht ziehen
Für zusätzliche Sicherheit eine Blacklist ungültiger Tokens führen:
Flight::route('POST /api/logout', function() {
$authHeader = Flight::request()->getHeader('Authorization');
preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches);
$jwt = $matches[1];
// Ablauf des Tokens extrahieren
$decoded = Flight::request()->data->user;
$ttl = $decoded->exp - time();
// In Cache/Redis bis zum Ablauf speichern
Flight::cache()->set("blacklist:$jwt", true, $ttl);
Flight::json(['message' => 'Erfolgreich abgemeldet']);
});
// Zu Ihrer JwtMiddleware hinzufügen:
public function before(array $params) {
// ... JWT extrahieren ...
// Blacklist überprüfen
if (Flight::cache()->get("blacklist:$jwt")) {
$this->app->jsonHalt(['error' => 'Token wurde widerrufen'], 401);
}
// ... Token überprüfen ...
}
Algorithmen und Schlüsseltypen
Firebase JWT unterstützt mehrere Algorithmen:
Symmetrische Algorithmen (HMAC)
- HS256 (Empfohlen für die meisten Apps): Verwendet einen einzelnen geheimen Schlüssel
- HS384, HS512: Stärkere Varianten
$jwt = JWT::encode($payload, $secretKey, 'HS256');
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
Asymmetrische Algorithmen (RSA/ECDSA)
- RS256, RS384, RS512: Verwendet Public/Private-Key-Paare
- ES256, ES384, ES512: Elliptische-Kurven-Varianten
// Schlüssel generieren: openssl genrsa -out private.key 2048
// openssl rsa -in private.key -pubout -out public.key
$privateKey = file_get_contents('/path/to/private.key');
$publicKey = file_get_contents('/path/to/public.key');
// Mit privatem Schlüssel kodieren
$jwt = JWT::encode($payload, $privateKey, 'RS256');
// Mit öffentlichem Schlüssel dekodieren
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
Wann RSA verwenden: Verwenden Sie RSA, wenn Sie den öffentlichen Schlüssel für die Verifizierung verteilen müssen (z. B. Microservices, Drittanbieter-Integrationen). Für eine einzelne Anwendung ist HS256 einfacher und ausreichend.
Fehlerbehebung
"Expired token" Fehler
Der exp-Anspruch Ihres Tokens liegt in der Vergangenheit. Geben Sie ein neues Token aus oder implementieren Sie Token-Refresh.
"Signature verification failed"
- Sie verwenden einen anderen geheimen Schlüssel zum Dekodieren als zum Kodieren
- Der Token wurde manipuliert
- Uhrzeitänderung zwischen Servern (fügen Sie einen Leeway-Puffer hinzu)
use Firebase\JWT\JWT;
JWT::$leeway = 60; // 60 Sekunden Uhrzeitänderung erlauben
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
Token wird in Anfragen nicht gesendet
Stellen Sie sicher, dass Ihr Client den Authorization-Header sendet:
// JavaScript-Beispiel
fetch('/api/users', {
headers: {
'Authorization': 'Bearer ' + token
}
});
Methoden
Die Firebase-JWT-Bibliothek stellt diese Kernmethoden zur Verfügung:
JWT::encode(array $payload, string $key, string $alg): Erstellt ein JWT aus einem PayloadJWT::decode(string $jwt, Key $key): Dekodiert und überprüft ein JWTJWT::urlsafeB64Encode(string $input): Base64 URL-sichere KodierungJWT::urlsafeB64Decode(string $input): Base64 URL-sichere DekodierungJWT::$leeway: Statische Eigenschaft, um Zeit-Leeway für die Validierung festzulegen (in Sekunden)
Warum diese Bibliothek verwenden?
- Branchenstandard: Firebase JWT ist die beliebteste und am weitesten vertrauenswürdige JWT-Bibliothek für PHP
- Aktive Wartung: Gepflegt vom Google/Firebase-Team
- Sicherheitsfokussiert: Regelmäßige Updates und Sicherheits-Patches
- Einfache API: Leicht zu verstehen und zu implementieren
- Gut dokumentiert: Umfangreiche Dokumentation und Community-Unterstützung
- Flexibel: Unterstützt mehrere Algorithmen und konfigurierbare Optionen
Siehe auch
- Firebase JWT Github Repository
- JWT.io - JWTs debuggen und dekodieren
- RFC 7519 - Offizielle JWT-Spezifikation
- Flight Middleware Dokumentation
- Flight Session Plugin - Für traditionelle sitzungsbasierte Authentifizierung
Lizenz
Die Firebase-JWT-Bibliothek ist unter der BSD 3-Clause License lizenziert. Details finden Sie im Github-Repository.
Awesome-plugins/n0nag0n_wordpress
WordPress-Integration: n0nag0n/wordpress-integration-for-flight-framework
Möchten Sie Flight PHP in Ihrer WordPress-Site verwenden? Dieses Plugin macht es zum Kinderspiel! Mit n0nag0n/wordpress-integration-for-flight-framework können Sie eine vollständige Flight-Anwendung direkt neben Ihrer WordPress-Installation ausführen – ideal zum Erstellen von benutzerdefinierten APIs, Mikroservices oder sogar vollwertigen Anwendungen, ohne WordPress zu verlassen.
Was tut es?
- Integriert Flight PHP nahtlos mit WordPress
- Leitet Anfragen an Flight oder WordPress basierend auf URL-Mustern weiter
- Organisieren Sie Ihren Code mit Controllern, Modellen und Ansichten (MVC)
- Richten Sie die empfohlene Flight-Ordnerstruktur einfach ein
- Verwenden Sie die WordPress-Datenbankverbindung oder Ihre eigene
- Feinabstimmung der Interaktion zwischen Flight und WordPress
- Einfache Admin-Oberfläche für die Konfiguration
Installation
- Laden Sie den Ordner
flight-integrationin Ihr/wp-content/plugins/-Verzeichnis hoch. - Aktivieren Sie das Plugin im WordPress-Admin-Bereich (Plugins-Menü).
- Gehen Sie zu Einstellungen > Flight Framework, um das Plugin zu konfigurieren.
- Legen Sie den Pfad zum Vendor-Ordner Ihrer Flight-Installation fest (oder verwenden Sie Composer, um Flight zu installieren).
- Konfigurieren Sie den Pfad zu Ihrem App-Ordner und erstellen Sie die Ordnerstruktur (das Plugin kann Ihnen dabei helfen!).
- Starten Sie mit der Erstellung Ihrer Flight-Anwendung!
Nutzungsbeispiele
Einfaches Route-Beispiel
In Ihrer Datei app/config/routes.php:
Flight::route('GET /api/hello', function() {
Flight::json(['message' => 'Hello World!']);
});
Controller-Beispiel
Erstellen Sie einen Controller in app/controllers/ApiController.php:
namespace app\controllers;
use Flight;
class ApiController {
public function getUsers() {
// Sie können WordPress-Funktionen in Flight verwenden!
$users = get_users();
$result = [];
foreach($users as $user) {
$result[] = [
'id' => $user->ID,
'name' => $user->display_name,
'email' => $user->user_email
];
}
Flight::json($result);
}
}
Dann in Ihrer routes.php:
Flight::route('GET /api/users', [app\controllers\ApiController::class, 'getUsers']);
FAQ
F: Muss ich Flight kennen, um dieses Plugin zu verwenden?
A: Ja, dies ist für Entwickler, die Flight innerhalb von WordPress nutzen möchten. Grundkenntnisse von Flights Routing und Anfrageverarbeitung werden empfohlen.
F: Wird dies meine WordPress-Site verlangsamen?
A: Nein! Das Plugin verarbeitet nur Anfragen, die zu Ihren Flight-Routen passen. Alle anderen Anfragen werden wie üblich an WordPress weitergeleitet.
F: Kann ich WordPress-Funktionen in meiner Flight-Anwendung verwenden?
A: Absolut! Sie haben vollen Zugriff auf alle WordPress-Funktionen, Hooks und Globals innerhalb Ihrer Flight-Routen und Controller.
F: Wie erstelle ich benutzerdefinierte Routes?
A: Definieren Sie Ihre Routes in der Datei config/routes.php in Ihrem App-Ordner. Schauen Sie sich die Beispieldatei an, die vom Ordnerstruktur-Generator erstellt wird.
Changelog
1.0.0
Erstveröffentlichung.
Für mehr Infos schauen Sie sich das GitHub-Repo an.
Awesome-plugins/ghost_session
Ghostff/Session
PHP-Sitzungsmanager (nicht blockierend, Flash, Segment, Sitzungsverschlüsselung). Verwendet PHP open_ssl für optionale Verschlüsselung/Entschlüsselung von Sitzungsdaten. Unterstützt File, MySQL, Redis und Memcached.
Klicken Sie hier, um den Code anzusehen.
Installation
Installieren Sie mit Composer.
composer require ghostff/session
Basic Configuration
Sie müssen nichts übergeben, um die Standardeinstellungen für Ihre Sitzung zu verwenden. Sie können mehr über Einstellungen in der Github Readme nachlesen.
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
// eine Sache, die man sich merken sollte, ist, dass Sie Ihre Sitzung bei jedem Seitenaufruf committen müssen
// oder Sie müssen auto_commit in Ihrer Konfiguration ausführen.
Simple Example
Hier ist ein einfaches Beispiel, wie Sie das verwenden könnten.
Flight::route('POST /login', function() {
$session = Flight::session();
// führen Sie hier Ihre Login-Logik aus
// Passwort validieren usw.
// wenn der Login erfolgreich ist
$session->set('is_logged_in', true);
$session->set('user', $user);
// immer wenn Sie in die Sitzung schreiben, müssen Sie sie explizit committen.
$session->commit();
});
// Diese Überprüfung könnte in der Logik der eingeschränkten Seite erfolgen oder mit Middleware umgeben sein.
Flight::route('/some-restricted-page', function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
// führen Sie hier Ihre Logik für die eingeschränkte Seite aus
});
// die Middleware-Version
Flight::route('/some-restricted-page', function() {
// reguläre Seitenslogik
})->addMiddleware(function() {
$session = Flight::session();
if(!$session->get('is_logged_in')) {
Flight::redirect('/login');
}
});
More Complex Example
Hier ist ein komplexeres Beispiel, wie Sie das verwenden könnten.
use Ghostff\Session\Session;
require 'vendor/autoload.php';
$app = Flight::app();
// geben Sie als ersten Argument einen benutzerdefinierten Pfad zu Ihrer Sitzungskonfigurationsdatei an
// oder geben Sie das benutzerdefinierte Array
$app->register('session', Session::class, [
[
// wenn Sie Ihre Sitzungsdaten in einer Datenbank speichern möchten (gut für Funktionen wie "mich von allen Geräten abmelden")
Session::CONFIG_DRIVER => Ghostff\Session\Drivers\MySql::class,
Session::CONFIG_ENCRYPT_DATA => true,
Session::CONFIG_SALT_KEY => hash('sha256', 'my-super-S3CR3T-salt'), // bitte ändern Sie das zu etwas anderem
Session::CONFIG_AUTO_COMMIT => true, // tun Sie das nur, wenn es erforderlich ist und/oder es schwierig ist, commit() für Ihre Sitzung aufzurufen.
// zusätzlich könnten Sie Flight::after('start', function() { Flight::session()->commit(); }); machen.
Session::CONFIG_MYSQL_DS => [
'driver' => 'mysql', # Database driver for PDO dns eg(mysql:host=...;dbname=...)
'host' => '127.0.0.1', # Database host
'db_name' => 'my_app_database', # Database name
'db_table' => 'sessions', # Database table
'db_user' => 'root', # Database username
'db_pass' => '', # Database password
'persistent_conn'=> false, # Vermeiden Sie den Aufwand, eine neue Verbindung bei jedem Skriptaufruf herzustellen, was zu einer schnelleren Web-Anwendung führt. FINDEN SIE DIE NACHTEILE SELBST
]
]
]);
Help! My Session Data is Not Persisting!
Setzen Sie Ihre Sitzungsdaten und sie persistieren nicht zwischen Anfragen? Sie haben vielleicht vergessen, Ihre Sitzungsdaten zu committen. Sie können das tun, indem Sie $session->commit() aufrufen, nachdem Sie Ihre Sitzungsdaten gesetzt haben.
Flight::route('POST /login', function() {
$session = Flight::session();
// führen Sie hier Ihre Login-Logik aus
// Passwort validieren usw.
// wenn der Login erfolgreich ist
$session->set('is_logged_in', true);
$session->set('user', $user);
// immer wenn Sie in die Sitzung schreiben, müssen Sie sie explizit committen.
$session->commit();
});
Die andere Möglichkeit, das zu umgehen, ist, wenn Sie Ihren Sitzungsdienst einrichten, auto_commit in Ihrer Konfiguration auf true setzen. Das wird Ihre Sitzungsdaten automatisch nach jeder Anfrage committen.
$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
$session->updateConfiguration([
Session::CONFIG_AUTO_COMMIT => true,
]);
}
);
Zusätzlich könnten Sie Flight::after('start', function() { Flight::session()->commit(); }); machen, um Ihre Sitzungsdaten nach jeder Anfrage zu committen.
Documentation
Besuchen Sie die Github Readme für die vollständige Dokumentation. Die Konfigurationsoptionen sind gut dokumentiert in der default_config.php-Datei selbst. Der Code ist einfach zu verstehen, wenn Sie dieses Paket selbst durchsehen möchten.
Awesome-plugins/mcp
FlightPHP MCP Server
Der FlightPHP MCP Server gibt jedem MCP-kompatiblen KI-Coding-Assistenten sofortigen, strukturierten Zugriff auf die gesamte FlightPHP-Dokumentation — Routing, Middleware, Plugins, Anleitungen und mehr. Statt dass Ihre KI API-Details halluziniert oder Methodensignaturen errät, holt sie die echten Dokumente bei Bedarf ab. Keine API-Schlüssel, keine Installation für die gehostete Version erforderlich.
Besuchen Sie das Github-Repository für den vollständigen Quellcode und Details.
Schnellstart
Der Server ist öffentlich gehostet und einsatzbereit:
https://mcp.flightphp.com/mcp
Fügen Sie einfach diese URL zu Ihrer KI-Coding-Erweiterung hinzu. Keine Anmeldung, keine Zugangsdaten. Siehe den Abschnitt IDE-Konfiguration unten für Kopier-Einfügen-Konfigurationen für die beliebtesten Tools.
Was es tut
Sobald verbunden, kann Ihr KI-Assistent:
- Alle verfügbaren Dokumente durchsuchen — jede Kern-Thema, Anleitung und Plugin-Seite auflisten
- Jede Dokumentationsseite abrufen — vollständigen Inhalt für Routing, Middleware, Anfragen, Sicherheit und mehr abrufen
- Plugin-Dokumente nachschlagen — vollständige Dokumentation für ActiveRecord, Session, Tracy, Runway und alle anderen offiziellen Plugins abrufen
- Schritt-für-Schritt-Anleitungen folgen — vollständige Durchgänge für den Aufbau von Blogs, REST-APIs und getesteten Anwendungen abrufen
- Über alles suchen — relevante Seiten in Kern-Dokumenten, Anleitungen und Plugins gleichzeitig finden
Wichtige Punkte
- Null Einrichtung — der gehostete Server unter
https://mcp.flightphp.com/mcperfordert keine Installation oder API-Schlüssel. - Immer aktuell — der Server holt Dokumente live von docs.flightphp.com, sodass er immer auf dem neuesten Stand ist.
- Funktioniert überall — jedes Tool, das den MCP Streamable HTTP-Transport unterstützt, kann sich verbinden.
- Selbst hostbar — Führen Sie Ihre eigene Instanz mit PHP >= 8.1 und Composer aus, wenn Sie möchten.
IDE / AI-Erweiterungskonfiguration
Der Server verwendet den Streamable HTTP-Transport. Wählen Sie Ihre Erweiterung unten aus und fügen Sie die Konfiguration ein.
Claude Code (CLI)
Führen Sie den folgenden Befehl aus, um es zu Ihrem Projekt hinzuzufügen:
claude mcp add --transport http flightphp-docs https://mcp.flightphp.com/mcp
Oder fügen Sie es manuell zu Ihrer Projektdatei .mcp.json hinzu:
{
"mcpServers": {
"flightphp-docs": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
}
GitHub Copilot (VS Code)
Fügen Sie es zu .vscode/mcp.json in Ihrem Arbeitsbereich hinzu:
{
"servers": {
"flightphp-docs": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
}
Kilo Code (VS Code)
Fügen Sie es zu Ihrer VS Code settings.json hinzu:
{
"kilocode.mcpServers": {
"flightphp-docs": {
"url": "https://mcp.flightphp.com/mcp",
"transport": "streamable-http"
}
}
}
Continue.dev (VS Code / JetBrains)
Fügen Sie es zu ~/.continue/config.json hinzu:
{
"mcpServers": [
{
"name": "flightphp-docs",
"transport": {
"type": "http",
"url": "https://mcp.flightphp.com/mcp"
}
}
]
}
Verfügbare Tools
Der MCP-Server stellt Ihrem KI-Assistenten die folgenden Tools zur Verfügung:
| Tool | Beschreibung |
|---|---|
list_docs_pages |
Listet alle verfügbaren Kern-Dokumentationsthemen mit Slugs und Beschreibungen auf |
get_docs_page |
Ruft eine Kern-Dokumentationsseite anhand des Themen-Slugs ab (z. B. routing, middleware, security) |
list_guide_pages |
Listet alle verfügbaren Schritt-für-Schritt-Anleitungen auf |
get_guide_page |
Ruft eine vollständige Anleitung anhand des Slugs ab (z. B. blog, unit-testing) |
list_plugin_pages |
Listet alle verfügbaren Plugin- und Erweiterungsseiten auf |
get_plugin_docs |
Ruft die vollständige Plugin-Dokumentation anhand des Slugs ab (z. B. active-record, session, jwt) |
search_docs |
Sucht über alle Dokumente, Anleitungen und Plugins nach einem Schlüsselwort oder Thema |
fetch_url |
Ruft jede Seite direkt anhand ihrer vollständigen docs.flightphp.com-URL ab |
Selbst-Hosting
Bevorzugen Sie es, Ihre eigene Instanz auszuführen? Sie benötigen PHP >= 8.1 und Composer.
git clone https://github.com/flightphp/mcp.git
cd mcp
composer install
php server.php
Der Server startet standardmäßig unter http://0.0.0.0:8890/mcp. Aktualisieren Sie Ihre IDE-Konfiguration, um auf Ihre lokale Adresse zu verweisen:
{
"mcpServers": {
"flightphp-docs": {
"type": "http",
"url": "http://localhost:8890/mcp"
}
}
}Awesome-plugins/async
Async
Async ist ein kleines Paket für das Flight-Framework, das es Ihnen ermöglicht, Ihre Flight-Apps in asynchronen Servern und Runtimes wie Swoole, AdapterMan, ReactPHP, Amp, RoadRunner, Workerman usw. auszuführen. Out of the box enthält es Adapter für Swoole und AdapterMan.
Das Ziel: Entwickeln und Debuggen mit PHP-FPM (oder dem integrierten Server) und Wechseln zu Swoole (oder einem anderen asynchronen Treiber) für die Produktion mit minimalen Änderungen.
Requirements
- PHP 7.4 oder höher
- Flight-Framework 3.16.1 oder höher
- Swoole-Erweiterung
Installation
Installieren Sie es über Composer:
composer require flightphp/async
Falls Sie mit Swoole ausführen möchten, installieren Sie die Erweiterung:
# using pecl
pecl install swoole
# or openswoole
pecl install openswoole
# or with a package manager (Debian/Ubuntu example)
sudo apt-get install php-swoole
Quick Swoole example
Unten ist eine minimale Einrichtung zu sehen, die zeigt, wie Sie sowohl PHP-FPM (oder den integrierten Server) als auch Swoole mit demselben Codebase unterstützen können.
Dateien, die Sie in Ihrem Projekt benötigen:
- index.php
- swoole_server.php
- SwooleServerDriver.php
index.php
Diese Datei ist ein einfacher Schalter, der die App im PHP-Modus für die Entwicklung erzwingt.
// index.php
<?php
define('NOT_SWOOLE', true);
include 'swoole_server.php';
swoole_server.php
Diese Datei bootstrapt Ihre Flight-App und startet den Swoole-Treiber, wenn NOT_SWOOLE nicht definiert ist.
// swoole_server.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
$app = Flight::app();
$app->route('/', function() use ($app) {
$app->json(['hello' => 'world']);
});
if (!defined('NOT_SWOOLE')) {
// Require the SwooleServerDriver class when running in Swoole mode.
require_once __DIR__ . '/SwooleServerDriver.php';
Swoole\Runtime::enableCoroutine();
$Swoole_Server = new SwooleServerDriver('127.0.0.1', 9501, $app);
$Swoole_Server->start();
} else {
$app->start();
}
SwooleServerDriver.php
Ein knapper Treiber, der zeigt, wie man Swoole-Anfragen in Flight über die AsyncBridge und die Swoole-Adapter überbrückt.
// SwooleServerDriver.php
<?php
use flight\adapter\SwooleAsyncRequest;
use flight\adapter\SwooleAsyncResponse;
use flight\AsyncBridge;
use flight\Engine;
use Swoole\HTTP\Server as SwooleServer;
use Swoole\HTTP\Request as SwooleRequest;
use Swoole\HTTP\Response as SwooleResponse;
class SwooleServerDriver {
protected $Swoole;
protected $app;
public function __construct(string $host, int $port, Engine $app) {
$this->Swoole = new SwooleServer($host, $port);
$this->app = $app;
$this->setDefault();
$this->bindWorkerEvents();
$this->bindHttpEvent();
}
protected function setDefault() {
$this->Swoole->set([
'daemonize' => false,
'dispatch_mode' => 1,
'max_request' => 8000,
'open_tcp_nodelay' => true,
'reload_async' => true,
'max_wait_time' => 60,
'enable_reuse_port' => true,
'enable_coroutine' => true,
'http_compression' => false,
'enable_static_handler' => true,
'document_root' => __DIR__,
'static_handler_locations' => ['/css', '/js', '/images', '/.well-known'],
'buffer_output_size' => 4 * 1024 * 1024,
'worker_num' => 4,
]);
$app = $this->app;
$app->map('stop', function (?int $code = null) use ($app) {
if ($code !== null) {
$app->response()->status($code);
}
});
}
protected function bindHttpEvent() {
$app = $this->app;
$AsyncBridge = new AsyncBridge($app);
$this->Swoole->on('Start', function(SwooleServer $server) {
echo "Swoole http server is started at http://127.0.0.1:9501\n";
});
$this->Swoole->on('Request', function (SwooleRequest $request, SwooleResponse $response) use ($AsyncBridge) {
$SwooleAsyncRequest = new SwooleAsyncRequest($request);
$SwooleAsyncResponse = new SwooleAsyncResponse($response);
$AsyncBridge->processRequest($SwooleAsyncRequest, $SwooleAsyncResponse);
$response->end();
gc_collect_cycles();
});
}
protected function bindWorkerEvents() {
$createPools = function() {
// create worker-specific connection pools here
};
$closePools = function() {
// close pools / cleanup here
};
$this->Swoole->on('WorkerStart', $createPools);
$this->Swoole->on('WorkerStop', $closePools);
$this->Swoole->on('WorkerError', $closePools);
}
public function start() {
$this->Swoole->start();
}
}
Running the server
- Entwicklung (PHP integrierter Server / PHP-FPM):
- php -S localhost:8000 (oder fügen Sie -t public/ hinzu, wenn Ihr index in public/ liegt)
- Produktion (Swoole):
- php swoole_server.php
Tipp: Für die Produktion verwenden Sie einen Reverse-Proxy (Nginx) vor Swoole, um TLS, statische Dateien und Lastverteilung zu handhaben.
Configuration notes
Der Swoole-Treiber stellt mehrere Konfigurationsoptionen zur Verfügung:
- worker_num: Anzahl der Worker-Prozesse
- max_request: Anfragen pro Worker vor dem Neustart
- enable_coroutine: Coroutines für Parallelität verwenden
- buffer_output_size: Ausgabepuffer-Größe
Passen Sie diese an Ihre Host-Ressourcen und Traffic-Muster an.
Error handling
AsyncBridge übersetzt Flight-Fehler in korrekte HTTP-Antworten. Sie können auch fehlerbehandlung auf Routenebene hinzufügen:
$app->route('/*', function() use ($app) {
try {
// route logic
} catch (Exception $e) {
$app->response()->status(500);
$app->json(['error' => $e->getMessage()]);
}
});
AdapterMan and other runtimes
AdapterMan wird als alternativer Runtime-Adapter unterstützt. Das Paket ist so konzipiert, dass es anpassbar ist – das Hinzufügen oder Verwenden anderer Adapter folgt im Allgemeinen demselben Muster: Konvertieren Sie die Server-Anfrage/Antwort in die Flight-Anfrage/Antwort über die AsyncBridge und die runtime-spezifischen Adapter.
Awesome-plugins/migrations
Migrationen
Eine Migration für Ihr Projekt verfolgt alle Datenbankänderungen, die mit Ihrem Projekt verbunden sind. byjg/php-migration ist eine wirklich hilfreiche Kernbibliothek, um Ihnen den Einstieg zu erleichtern.
Installation
PHP-Bibliothek
Wenn Sie nur die PHP-Bibliothek in Ihrem Projekt verwenden möchten:
composer require "byjg/migration"
Befehlszeilenschnittstelle
Die Befehlszeilenschnittstelle ist eigenständig und erfordert keine Installation mit Ihrem Projekt.
Sie können global installieren und einen symbolischen Link erstellen.
composer require "byjg/migration-cli"
Bitte besuchen Sie byjg/migration-cli für weitere Informationen zur Migration CLI.
Unterstützte Datenbanken
| Datenbank | Treiber | Verbindungszeichenfolge |
|---|---|---|
| Sqlite | pdo_sqlite | sqlite:///path/to/file |
| MySql/MariaDb | pdo_mysql | mysql://username:password@hostname:port/database |
| Postgres | pdo_pgsql | pgsql://username:password@hostname:port/database |
| Sql Server | pdo_dblib, pdo_sysbase Linux | dblib://username:password@hostname:port/database |
| Sql Server | pdo_sqlsrv Windows | sqlsrv://username:password@hostname:port/database |
Wie funktioniert es?
Die Datenbankmigration verwendet reines SQL, um das Datenbankversioning zu verwalten. Um es zum Laufen zu bringen, müssen Sie:
- Die SQL-Skripte erstellen
- Verwenden Sie die Befehlszeile oder die API.
Die SQL-Skripte
Die Skripte sind in drei Gruppen unterteilt:
- Das BASIS-Skript enthält alle SQL-Befehle zum Erstellen einer frischen Datenbank;
- Die UP-Skripte enthalten alle SQL-Migrationsbefehle, um die Datenbankversion "hoch" zu setzen;
- Die DOWN-Skripte enthalten alle SQL-Migrationsbefehle, um die Datenbankversion "herunter" zu setzen oder zurückzusetzen;
Das Verzeichnis der Skripte ist :
<root dir>
|
+-- base.sql
|
+-- /migrations
|
+-- /up
|
+-- 00001.sql
+-- 00002.sql
+-- /down
|
+-- 00000.sql
+-- 00001.sql
- "base.sql" ist das Basisskript
- Der "up"-Ordner enthält die Skripte für das Hochsetzen der Version. Zum Beispiel: 00002.sql ist das Skript, um die Datenbank von Version '1' auf '2' zu migrieren.
- Der "down"-Ordner enthält die Skripte für das Heruntersetzen der Version. Zum Beispiel: 00001.sql ist das Skript, um die Datenbank von Version '2' auf '1' zu migrieren. Der "down"-Ordner ist optional.
Multi-Entwicklungsumgebung
Wenn Sie mit mehreren Entwicklern und mehreren Branches arbeiten, ist es schwierig zu bestimmen, welche Nummer die nächste ist.
In diesem Fall haben Sie das Suffix "-dev" nach der Versionsnummer.
Sehen Sie sich das Szenario an:
- Entwickler 1 erstellt einen Branch und die aktuellste Version ist z.B. 42.
- Entwickler 2 erstellt gleichzeitig einen Branch und hat die gleiche Datenbankversionsnummer.
In beiden Fällen werden die Entwickler eine Datei mit dem Namen 43-dev.sql erstellen. Beide Entwickler werden ohne Probleme hoch und runter migrieren können, und Ihre lokale Version wird 43 sein.
Aber Entwickler 1 hat seine Änderungen zusammengeführt und eine endgültige Version 43.sql erstellt (git mv 43-dev.sql 43.sql). Wenn Entwickler 2 seinen lokalen Branch aktualisiert, hat er eine Datei 43.sql (von dev 1) und seine Datei 43-dev.sql.
Wenn er versucht, hoch oder runter zu migrieren,
wird das Migrationsskript abgebrochen und ihn warnen, dass es zwei Versionen 43 gibt. In diesem Fall muss Entwickler 2 seine Datei in 44-dev.sql aktualisieren und weiterarbeiten, bis er die Änderungen zusammenführt und eine endgültige Version generiert.
Verwendung der PHP-API und Integration in Ihre Projekte
Die grundlegende Verwendung ist
- Erstellen Sie eine Verbindung zu einem ConnectionManagement-Objekt. Für weitere Informationen siehe die Komponente "byjg/anydataset".
- Erstellen Sie ein Migrationsobjekt mit dieser Verbindung und dem Ordner, in dem sich die SQL-Skripte befinden.
- Verwenden Sie den richtigen Befehl für "reset", "up" oder "down" der Migrationsskripte.
Sehen Sie sich ein Beispiel an:
<?php
// Erstellen Sie die Verbindungs-URI
// Weitere Informationen: https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');
// Registrieren Sie die Datenbank oder Datenbanken, die diese URI verarbeiten können:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Erstellen Sie die Migrationsinstanz
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Fügen Sie eine Callback-Fortschrittsfunktion hinzu, um Informationen von der Ausführung zu erhalten
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
echo "$action, $currentVersion, ${fileInfo['description']}\n";
});
// Stellen Sie die Datenbank mit dem "base.sql"-Skript wieder her
// und führen Sie ALLE vorhandenen Skripte aus, um die Datenbankversion auf die neueste Version zu bringen
$migration->reset();
// Führen Sie ALLE vorhandenen Skripte für hoch oder runter die Datenbankversion aus
// von der aktuellen Version bis zur $version-Nummer;
// Wenn die Versionsnummer nicht angegeben ist, migrieren Sie bis zur letzten Datenbankversion
$migration->update($version = null);
Das Migrationsobjekt steuert die Datenbankversion.
Erstellen einer Versionskontrolle in Ihrem Projekt
<?php
// Registrieren Sie die Datenbank oder Datenbanken, die diese URI verarbeiten können:
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);
// Erstellen Sie die Migrationsinstanz
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');
// Dieser Befehl erstellt die Versions-Tabelle in Ihrer Datenbank
$migration->createVersion();
Aktuelle Version abrufen
<?php
$migration->getCurrentVersion();
Callback zum Steuern des Fortschritts hinzufügen
<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
echo "Befehl ausführen: $command bei Version $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});
Instanz des Db-Treibers abrufen
<?php
$migration->getDbDriver();
Um es zu verwenden, besuchen Sie bitte: https://github.com/byjg/anydataset-db
Teilweise Migration vermeiden (nicht verfügbar für MySQL)
Eine partielle Migration ist, wenn das Migrationsskript in der Mitte des Prozesses aufgrund eines Fehlers oder einer manuellen Unterbrechung unterbrochen wird.
Die Migrationstabelle hat den Status partial up oder partial down und muss manuell behoben werden, bevor sie wieder migrieren kann.
Um diese Situation zu vermeiden, können Sie angeben, dass die Migration in einem Transaktionskontext ausgeführt wird.
Wenn das Migrationsskript fehlschlägt, wird die Transaktion zurückgesetzt und die Migrationstabelle wird als complete markiert und
die Version wird die unmittelbar vorherige Version vor dem Skript sein, das den Fehler verursacht hat.
Um diese Funktion zu aktivieren, müssen Sie die Methode withTransactionEnabled aufrufen und true als Parameter übergeben:
<?php
$migration->withTransactionEnabled(true);
HINWEIS: Diese Funktion ist nicht für MySQL verfügbar, da es DDL-Befehle innerhalb einer Transaktion nicht unterstützt. Wenn Sie diese Methode mit MySQL verwenden, ignoriert die Migration sie stillschweigend. Weitere Informationen: https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html
Tipps zum Schreiben von SQL-Migrationen für Postgres
Zur Erstellung von Triggern und SQL-Funktionen
-- DO
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Überprüfen, ob empname und salary angegeben sind
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname darf nicht null sein'; -- es ist egal, ob diese Kommentare leer sind oder nicht
END IF; --
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% darf kein null Gehalt haben', NEW.empname; --
END IF; --
-- Wer arbeitet für uns, wenn sie dafür bezahlen müssen?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% darf kein negatives Gehalt haben', NEW.empname; --
END IF; --
-- Merken Sie, wer die Gehaltsliste wann geändert hat
NEW.last_date := current_timestamp; --
NEW.last_user := current_user; --
RETURN NEW; --
END; --
$emp_stamp$ LANGUAGE plpgsql;
-- DON'T
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Überprüfen, ob empname und salary angegeben sind
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname darf nicht null sein';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% darf kein null Gehalt haben', NEW.empname;
END IF;
-- Wer arbeitet für uns, wenn sie dafür bezahlen müssen?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% darf kein negatives Gehalt haben', NEW.empname;
END IF;
-- Merken Sie, wer die Gehaltsliste wann geändert hat
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
Da die PDO-Datenbank-Abstraktionsschicht keine Batchverarbeitungen von SQL-Anweisungen ausführen kann,
muss byjg/migration, wenn es eine Migrationsdatei liest, den gesamten Inhalt der SQL-Datei an den Semikolons aufteilen und die Anweisungen einzeln ausführen. Es gibt jedoch eine Art von
Anweisung, die mehrere Semikolons in ihrem Körper haben kann: Funktionen.
Um Funktionen korrekt parsen zu können, begann byjg/migration 2.1.0 damit, Migrationsdateien an der
Semikolon + EOL-Sequenz anstelle nur am Semikolon aufzuteilen. Auf diese Weise kann byjg/migration sie parsen, wenn Sie nach jedem inneren Semikolon einer Funktionsdefinition einen leeren Kommentar anhängen.
Leider wird die Bibliothek die CREATE FUNCTION-Anweisung in mehrere Teile aufteilen und die Migration wird fehlschlagen, wenn Sie vergessen, diese Kommentare hinzuzufügen.
Vermeidung des Doppelpunktzeichens (:)
-- DO
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (CAST(booked_at AS DATE) <= check_in),
check_in DATE NOT NULL
);
-- DON'T
CREATE TABLE bookings (
booking_id UUID PRIMARY KEY,
booked_at TIMESTAMPTZ NOT NULL CHECK (booked_at::DATE <= check_in),
check_in DATE NOT NULL
);
Da PDO das Doppelpunkt-Zeichen verwendet, um benannte Parameter in vorbereiteten Anweisungen zu kennzeichnen, führt die Verwendung zu Problemen in anderen Kontexten.
Zum Beispiel können PostgreSQL-Anweisungen :: verwenden, um Werte zwischen Typen zu casten. Auf der anderen Seite wird PDO dies als ungültigen benannten Parameter in einem ungültigen Kontext behandeln und fehlschlagen, wenn er versucht, es auszuführen.
Die einzige Möglichkeit, diese Inkonsistenz zu beheben, besteht darin, Doppelpunkte ganz zu vermeiden (in diesem Fall hat PostgreSQL auch eine alternative
Syntax: CAST(value AS type)).
Verwenden Sie einen SQL-Editor
Abschließend kann das Schreiben manueller SQL-Migrationen mühsam sein, aber es ist deutlich einfacher, wenn Sie einen Editor verwenden, der die SQL-Syntax versteht, Autovervollständigung bietet, Ihr aktuelles Datenbankschema inspiziert und/oder Ihren Code automatisch formatiert.
Umgang mit unterschiedlichen Migrationen innerhalb eines Schemas
Wenn Sie unterschiedliche Migrationsskripte und Versionen innerhalb desselben Schemas erstellen müssen, ist dies möglich, aber es ist zu riskant und ich empfehle es nicht.
Um dies zu tun, müssen Sie unterschiedliche "Migrationstabellen" erstellen, indem Sie den Parameter an den Konstruktor übergeben.
<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEUER_MIGRATIONSTABELLENNAME");
Aus Sicherheitsgründen ist diese Funktion nicht über die Befehlszeile verfügbar, aber Sie können die Umgebungsvariable
MIGRATION_VERSION verwenden, um den Namen zu speichern.
Wir empfehlen dringend, diese Funktion nicht zu verwenden. Die Empfehlung ist, eine Migration für ein Schema durchzuführen.
Ausführen von Unit-Tests
Basis-Unit-Tests können ausgeführt werden mit:
vendor/bin/phpunit
Ausführen von Datenbanktests
Integrationstests erfordern, dass Sie die Datenbanken online und verfügbar haben. Wir haben ein einfaches docker-compose.yml bereitgestellt, und Sie
können es verwenden, um die Datenbanken für Tests zu starten.
Ausführen der Datenbanken
docker-compose up -d postgres mysql mssql
Führen Sie die Tests aus
vendor/bin/phpunit
vendor/bin/phpunit tests/SqliteDatabase*
vendor/bin/phpunit tests/MysqlDatabase*
vendor/bin/phpunit tests/PostgresDatabase*
vendor/bin/phpunit tests/SqlServerDblibDatabase*
vendor/bin/phpunit tests/SqlServerSqlsrvDatabase*
Optional können Sie den Host und das Passwort, das von den Unit-Tests verwendet wird, festlegen.
export MYSQL_TEST_HOST=localhost # standardmäßig localhost
export MYSQL_PASSWORD=newpassword # verwenden Sie '.' wenn Sie ein leeres Passwort haben möchten
export PSQL_TEST_HOST=localhost # standardmäßig localhost
export PSQL_PASSWORD=newpassword # verwenden Sie '.' wenn Sie ein leeres Passwort haben möchten
export MSSQL_TEST_HOST=localhost # standardmäßig localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db # standardmäßig /tmp/test.dbAwesome-plugins/flightmail
FlightMail
Drittanbieter-Plugin - gepflegt von Ryan Stubbs (ryanstubbs/flightmail, MIT-lizenziert). Nicht Teil von Flight Core - bitte melden Sie Probleme im GitHub-Repository.
ryanstubbs/flightmail lässt Sie E-Mails aus Ihrer Flight-App versenden, ohne die Kopfschmerzen. Es umhüllt Symfony Mailer - die am härtesten erprobte Mail-Bibliothek in PHP - und lässt sie sich anfühlen wie ein Teil von Flight. Eine Zeile zum Installieren, eine fließende Kette zum Senden:
Flight::mail()->compose()
->to('someone@example.com')
->subject('Geschafft!')
->text('Ihre erste E-Mail ist unterwegs.')
->send();
Funktionen
- Jeder Anbieter, jeweils eine Zeile. SMTP, Postmark, Sendgrid, Mailgun, Amazon SES, Brevo und Co. funktionieren alle über einfache DSN-Strings.
- Mehrere Anbieter gleichzeitig nutzen. Transaktionsmails über Postmark, Newsletter über Ihr eigenes SMTP - pro Nachricht wählen.
- Templates, wenn Sie sie wollen. Bodies mit Twig oder Latte rendern. Keine Templates? Einfach Strings übergeben und nichts Extra installieren.
- Feinschliff beim Senden. Optionales CSS-Inlining und automatische Klartext-Teile, die aus Ihrem HTML abgeleitet werden, angetrieben von Bibliotheken, die Sie nur installieren, wenn Sie sie nutzen.
- Langweilig auf die beste Art. Lazy Connections, klare Fehler statt stillschweigend verschluckter Mails, und alles ist austauschbar, wenn Sie etwas Eigenes brauchen.
Anforderungen
| Was | Version |
|---|---|
| PHP | 8.2 oder neuer |
| Flight PHP | core ^3.15 |
| Symfony Mailer | ^7.2 oder ^8.0 (wird automatisch installiert) |
Installation
composer require ryanstubbs/flightmail
Das war's für das Senden von Klartext- und HTML-E-Mails. Template-Rendering ist optional - fügen Sie eine Engine nur hinzu, wenn Sie sie nutzen:
composer require twig/twig # für .twig-Templates
composer require latte/latte # für .latte-Templates
Zwei weitere optionale Bibliotheken treiben die Verbesserungen beim Senden an, die weiter unten beschrieben werden:
composer require pelago/emogrifier # für CSS-Inlining ("inline_css")
composer require league/html-to-markdown # für Markdown-Textteile ("text_from_html")
Alle davon können nebeneinander installiert werden; FlightMail wählt anhand Ihrer Konfiguration die richtige aus.
Ihre erste E-Mail
Fügen Sie das in Ihren Bootstrap ein (dieselbe Stelle, an der Sie Routen definieren):
<?php
require 'vendor/autoload.php';
use ryanstubbs\FlightMail\MailPlugin;
// FlightMail sagen, von wo und worüber Mail gesendet werden soll.
MailPlugin::install([
'dsns' => [
'default' => 'smtp://user:pass@localhost:1025',
],
'from' => 'no-reply@example.com',
]);
Flight::route('/signup', function () {
Flight::mail()->compose()
->to('new-user@example.com')
->subject('Willkommen an Bord!')
->html('<h1>Willkommen!</h1><p>Wir freuen uns, dass Sie da sind.</p>')
->send();
});
Flight::start();
Nutzen Sie das Flight PHP Skeleton? Registrieren Sie es in app/config/services.php im Instanz-Stil:
use ryanstubbs\FlightMail\MailPlugin;
MailPlugin::register($app, [
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'from' => 'no-reply@example.com',
]);
Beide Stile stellen denselben Mailer bereit: Flight::mail() und $app->mail() sind austauschbar.
Lokal testen? Wenn Ihr Projekt in DDEV läuft, zeigen Sie den DSN auf
smtp://127.0.0.1:1025und lesen Sie jede erfasste E-Mail in Mailpit unterhttp://<project>.ddev.site:8025. Nichts verlässt Ihren Rechner.
E-Mails senden
Einfache Strings (keine Template-Engine nötig)
->text() und ->html() nehmen Roh-Strings und brauchen sonst nichts Installiertes:
Flight::mail()->compose()
->to('ops@example.com')
->subject('Backup abgeschlossen')
->text('Nächtliches Backup in 42 Minuten abgeschlossen.')
->send();
Flight::mail()->compose()
->to('billing@example.com')
->subject('Rechnung #123')
->html('<h1>Rechnung #123</h1><p>Gesamtbetrag: $42.00</p>')
->send();
Twig-Templates
// welcome.html.twig enthält: Hallo {{ name }}, danke für die Anmeldung!
Flight::mail()->compose()
->to('someone@example.com')
->subject('Willkommen!')
->template('welcome.html.twig', ['name' => 'Ryan'])
->send();
Latte-Templates
Dieselbe Idee, .latte-Erweiterung:
// welcome.latte enthält: Hallo {$name}, danke für die Anmeldung!
Flight::mail()->compose()
->to('someone@example.com')
->subject('Willkommen!')
->template('welcome.latte', ['name' => 'Ryan'])
->send();
HTML + Klartext zusammen
Best Practice für die Zustellbarkeit - geben Sie Mail-Clients beide Versionen:
Flight::mail()->compose()
->to('someone@example.com')
->subject('Willkommen!')
->template('welcome.html.twig', ['name' => 'Ryan']) // reichhaltige Version
->textTemplate('welcome.txt.twig', ['name' => 'Ryan']) // Fallback-Version
->send();
Ein paar Dinge, die sich bei Templates zu wissen lohnen:
- Sie werden lazy gerendert, zum Sendezeitpunkt - jetzt zusammenbauen, später rendern.
- Die Engine wird anhand der Erweiterung gewählt:
.twig→ Twig,.latte→ Latte, alles andere → Ihr konfigurierter Standard (renderer-Option). - Ein expliziter
->html()- oder->text()-Body gewinnt immer gegenüber einem Template, sodass Sie ein Standard-Template setzen und es pro Nachricht überschreiben können.
HTML stylen und Textteile erzeugen
Zwei optionale Verbesserungen beim Senden, beide standardmäßig aus und beide angetrieben von Bibliotheken, die Sie nur installieren, wenn Sie sie wollen:
| Funktion | Installation | Config-Schlüssel |
|---|---|---|
| CSS-Inlining | pelago/emogrifier |
inline_css |
| Textteil aus HTML | league/html-to-markdown |
text_from_html |
CSS in HTML-E-Mails inlinen
Gmail und die meisten Webmail-Clients entfernen <style>-Blöcke - inline style=""-Attribute sind das einzige Styling, das sie zuverlässig ehren. Die von Hand zu schreiben ist miserabel; lassen Sie Emogrifier das zum Sendezeitpunkt erledigen:
composer require pelago/emogrifier
MailPlugin::install([
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'inline_css' => true,
]);
Wenn das an ist, bekommt jeder HTML-Body sein CSS direkt vor dem Senden inline gesetzt - egal ob er aus einem Template oder ->html() kam. Eine Nachricht wie <style>p { color: red; }</style><p>Hallo</p> geht als <p style="color: red;">Hallo</p> raus.
Um gemeinsame Styles in jede E-Mail einzuspritzen (Markenfarben, Resets), ohne sie in jedem Template zu wiederholen, übergeben Sie Regeln direkt oder zeigen Sie auf eine Stylesheet-Datei:
'inline_css' => ['css_file' => __DIR__ . '/mail-styles/base.css'],
// oder
'inline_css' => ['css' => '.button { background: #0a84ff; color: #fff; }'],
Steuerung pro Nachricht:
$message->inlineCss(); // Inlining für diese eine Nachricht erzwingen
$message->withoutInlineCss(); // überspringen, selbst wenn global aktiviert
Den Textteil aus Ihrem HTML erzeugen
Best Practice ist, eine HTML- und eine Klartext-Version zusammen zu senden, aber beides zu schreiben ist mühsam. FlightMail kann den Textteil automatisch aus dem finalen HTML ableiten - die Basis-Konvertierung braucht keine Extra-Abhängigkeit, da der Converter mit Symfony Mime mitgeliefert wird:
MailPlugin::install([
'dsns' => ['default' => 'smtp://user:pass@localhost:1025'],
'text_from_html' => true, // Markdown wenn möglich, sonst Klartext
]);
Modi:
trueoder'auto'- Markdown-Ausgabe, wennleague/html-to-markdowninstalliert ist, sonst einfaches Entfernen der Tags.'markdown'- Markdown erzwingen (composer require league/html-to-markdown; Überschriften werden zu==, Links[text](url), fett**bold**).'plain'- immer Tags entfernen; funktioniert ohne Extra-Pakete.
Die Erzeugung läuft nach dem Rendern und CSS-Inlining und nur, wenn die Nachricht einen HTML-Body, aber keinen Text-Body hat - ein explizites ->text() oder ->textTemplate() gewinnt immer. Überschreibungen pro Nachricht spiegeln das Inlining:
$message->textFromHtml('plain'); // Tag-Stripping für diese eine erzwingen
$message->withoutTextFromHtml(); // nur-HTML-E-Mail
Aktivieren Sie einen Modus, dessen Bibliothek nicht installiert ist, und Sie bekommen einen klaren Fehler, der das genaue composer require nennt, das Sie ausführen müssen - niemals stiller Qualitätsverlust.
Einen Anbieter wählen
Anbieter werden über DSN-Strings eingebunden. Installieren Sie das Bridge-Paket, fügen Sie den DSN in dsns ein, fertig.
| Anbieter | Installation | DSN-Beispiel |
|---|---|---|
| SMTP | eingebaut | smtp://user:pass@host:587 |
| Sendmail | eingebaut | sendmail://default |
| Dev/null (Mails verwerfen) | eingebaut | null://null |
| Postmark | composer require symfony/postmark-mailer |
postmark+api://KEY@api.postmarkapp.com |
| Sendgrid | composer require symfony/sendgrid-mailer |
sendgrid+api://KEY@default |
| Mailgun | composer require symfony/mailgun-mailer |
mailgun+https://KEY:DOMAIN@api.mailgun.net |
| Amazon SES | composer require symfony/amazon-mailer |
ses+https://KEY:SECRET@default |
| Brevo | composer require symfony/brevo-mailer |
brevo+api://KEY@default |
| MailerSend | composer require symfony/mailersend-mailer |
mailersend+api://KEY@default |
Die vollständige Liste steht in den Symfony Mailer-Docs - alles, was dort dokumentiert ist, funktioniert hier unverändert.
Mehrere Anbieter gleichzeitig
Benennen Sie jeden Transport und wählen Sie dann pro Nachricht:
MailPlugin::install([
'dsns' => [
'transactional' => 'postmark+api://KEY@api.postmarkapp.com',
'bulk' => 'smtp://user:pass@bulk.example.com:587',
],
'from' => 'no-reply@example.com',
]);
// Kein ->transport()-Aufruf = erster Schlüssel in "dsns" ("transactional" hier).
Flight::mail()->compose()->to('...')->text('Beleg')->send();
// Explizit eine andere Route wählen.
Flight::mail()->compose()->to('...')->text('Newsletter')->transport('bulk')->send();
Konfigurationsreferenz
Alles ist optional außer dsns.
MailPlugin::install([
// ERFORDERLICH - Transportname => Symfony DSN.
// Der erste Eintrag wird verwendet, wenn eine Nachricht keinen nennt.
'dsns' => [
'default' => 'smtp://user:pass@localhost:1025',
],
// Transport, der verwendet wird, wenn eine Nachricht kein explizites
// ->transport() hat und Sie nicht den ersten Schlüssel wollen. Muss in "dsns" existieren.
'default_transport' => 'default',
// Globaler Absender. String, Symfony Address oder ['email' => 'Name'].
// Wird nur angewendet, wenn eine Nachricht kein eigenes ->from() setzt.
'from' => ['no-reply@example.com' => 'Meine App'],
// Standard-Template-Engine: 'twig', 'latte' oder ein eigener Name.
// Wird nur für Templates konsultiert, deren Erweiterung kein registrierter Renderer ist.
'renderer' => 'twig',
// Wo Templates liegen, der Reihe nach durchsucht; plus ein optionales Cache-Verzeichnis.
'templates' => [
'paths' => [__DIR__ . '/mail-templates'],
'cache' => __DIR__ . '/cache/mail',
],
// Extra-Optionen, die direkt an Twig\Environment übergeben werden.
'twig' => ['options' => ['strict_variables' => true]],
// Die Latte-Engine beim Boot anpassen: fn(Latte\Engine $engine): void.
'latte' => ['setup' => static fn (Latte\Engine $e) => $e->addExtension(new MyExtension())],
// Body-Verbesserungen beim Senden (siehe "HTML stylen und Textteile erzeugen").
'inline_css' => true, // oder ['css' => '...', 'css_file' => '...']
'text_from_html' => true, // oder 'plain' / 'markdown'
// Eigene DSN-Schemata, eigene Renderer, Pre-Send-Hooks (siehe unten).
'transport_factories' => [],
'renderers' => [],
'hooks' => [],
// Optionale Infrastruktur, die jedem Transport übergeben wird.
'event_dispatcher' => $dispatcher, // Symfony MessageEvents
'logger' => $psr3Logger,
]);
Noch weiter gehen
Alles darunter ist optional. Die Defaults decken die meisten Apps ab.
Ein eigenes DSN-Schema hinzufügen
Implementieren Sie Symfonys TransportFactoryInterface und registrieren Sie es - dann funktioniert Ihr eigenes Schema genau wie ein eingebautes:
use ryanstubbs\FlightMail\MailPlugin;
use Symfony\Component\Mailer\Transport\Dsn;
use Symfony\Component\Mailer\Transport\TransportFactoryInterface;
use Symfony\Component\Mailer\Transport\TransportInterface;
class MyCarrierFactory implements TransportFactoryInterface
{
public function supports(Dsn $dsn): bool
{
return $dsn->getScheme() === 'mycarrier';
}
public function create(Dsn $dsn): TransportInterface
{
// ... einen Transport bauen, der mit Ihrem Carrier spricht
}
}
$plugin = MailPlugin::install(['dsns' => ['carrier' => 'mycarrier://key']]);
$plugin->addTransportFactory(new MyCarrierFactory());
Einen eigenen Template-Renderer hinzufügen
Alles, das einen Template-Namen plus Parameter in einen String verwandelt, qualifiziert sich:
use ryanstubbs\FlightMail\MailPlugin;
use ryanstubbs\FlightMail\Render\RendererInterface;
$plugin = MailPlugin::install($config);
$plugin->addRenderer('markdown', fn (array $config): RendererInterface =>
new MarkdownMailRenderer($config['templates']['paths'] ?? [])
);
// Templates, die auf .markdown enden, nutzen ihn jetzt automatisch:
Flight::mail()->compose()->to('...')->template('welcome.markdown', ['name' => 'Ryan'])->send();
Etwas direkt vor dem Senden ausführen
Hooks erhalten die fertige Nachricht - nach dem Rendern, nach den Defaults, direkt vor dem Draht:
$plugin->addHook(function (ryanstubbs\FlightMail\Message $message): void {
$message->getHeaders()->addTextHeader('X-Mailer', 'MyApp/1.0');
});
Events und Logging
Übergeben Sie einen Symfony Event Dispatcher und/oder PSR-3 Logger und jeder Transport wird sie nutzen:
$plugin->eventDispatcher($dispatcher); // empfängt MessageEvent vor jedem Senden
$plugin->logger($logger); // Logs auf Transport-Ebene
API-Spickzettel
// Einrichtung
MailPlugin::install($config) // auf der globalen Flight-App registrieren
MailPlugin::register($app, $config) // auf einer bestimmten Engine registrieren
$mailer = Flight::mail(); // die gemeinsame Mailer-Instanz
// Nachrichten bauen
$mailer->compose(): Message
$message->to(...)->from(...)->subject(...) // Standard-Symfony-Mime-Methoden
$message->text(string) // Klartext-String-Body
$message->html(string) // HTML-String-Body
$message->template($name, $params) // HTML-Body aus einem Template
$message->htmlTemplate($name, $params) // Alias von template()
$message->textTemplate($name, $params) // Text-Body aus einem Template
$message->inlineCss() / ->withoutInlineCss() // CSS-Inlining pro Nachricht
$message->textFromHtml($mode) // Auto-Textteil: true/'auto'/'plain'/'markdown'/false
$message->withoutTextFromHtml() // nur-HTML-E-Mail
$message->transport($name) // über einen benannten DSN leiten
$message->send(): ?SentMessage // rendern + senden
// Am Mailer selbst
$mailer->send($message): ?SentMessage // explizite Alternative zu $message->send()
$mailer->render($template, $params): string // rendern ohne zu senden
$mailer->addHook(callable): static // fn(Message $message): void
$mailer->transports(): TransportManager // get() / has() / names()
$mailer->renderers(): RendererFactory // create() / has() / add()
Da Message Symfony\Component\Mime\Email erweitert, funktioniert jede Symfony-Methode, die Sie schon kennen - attach(), embed(), priority(), replyTo() - sofort.
Fehlerbehebung
"No mail DSNs configured"
Sie haben Flight::mail() aufgerufen, bevor das Plugin registriert war, oder das Config-Array enthielt kein dsns. Dieser Fehler ist Absicht - FlightMail weigert sich zu raten, wohin Ihre Mail soll, statt sie stillschweigend zu verwerfen.
"Unknown mail template renderer ..."
Sie haben ein Template verwendet, dessen Engine nicht installiert ist. Beheben Sie das mit composer require twig/twig oder composer require latte/latte, oder registrieren Sie einen eigenen Renderer, der nach der Erweiterung benannt ist.
"Unknown mail transport ..."
Ein ->transport('name') (oder default_transport) passt zu keinem Schlüssel in dsns. Prüfen Sie die Schreibweise - der Fehler listet die konfigurierten Namen.
E-Mail kommt nicht an
Zeigen Sie dsns auf null://null, um zu bestätigen, dass der Rest Ihres Codes funktioniert, und wechseln Sie dann zurück zum echten DSN. In DDEV nutzen Sie smtp://127.0.0.1:1025 und prüfen Sie Nachrichten in Mailpit auf Port 8025.
Für Fehlerberichte, Pull Requests und den vollständigen Quellcode besuchen Sie das GitHub-Repository.
Awesome-plugins/comment_template
CommentTemplate
CommentTemplate ist ein leistungsstarker PHP-Template-Engine mit Asset-Kompilierung, Template-Vererbung und Variablenverarbeitung. Es bietet eine einfache, aber flexible Möglichkeit, Templates zu verwalten, mit integrierter CSS/JS-Minifizierung und Caching.
Features
- Template-Vererbung: Verwenden von Layouts und Einbinden anderer Templates
- Asset-Kompilierung: Automatische CSS/JS-Minifizierung und Caching
- Variablenverarbeitung: Template-Variablen mit Filtern und Befehlen
- Base64-Kodierung: Inline-Assets als Data-URIs
- Flight Framework Integration: Optionale Integration mit dem Flight PHP Framework
Installation
Installieren Sie es mit Composer.
composer require knifelemon/comment-template
Grundlegende Konfiguration
Es gibt einige grundlegende Konfigurationsoptionen, um zu starten. Sie können mehr darüber in der CommentTemplate Repo lesen.
Methode 1: Verwendung einer Callback-Funktion
<?php
require_once 'vendor/autoload.php';
use KnifeLemon\CommentTemplate\Engine;
$app = Flight::app();
$app->register('view', Engine::class, [], function (Engine $engine) use ($app) {
// Root-Verzeichnis (wo index.php liegt) - das Dokument-Root Ihrer Web-Anwendung
$engine->setPublicPath(__DIR__);
// Verzeichnis für Template-Dateien - unterstützt sowohl relative als auch absolute Pfade
$engine->setSkinPath('views'); // Relativ zum Public Path
// Wo kompilierte Assets gespeichert werden - unterstützt sowohl relative als auch absolute Pfade
$engine->setAssetPath('assets'); // Relativ zum Public Path
// Dateierweiterung für Templates
$engine->setFileExtension('.php');
});
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
Methode 2: Verwendung von Konstruktor-Parametern
<?php
require_once 'vendor/autoload.php';
use KnifeLemon\CommentTemplate\Engine;
$app = Flight::app();
// __construct(string $publicPath = "", string $skinPath = "", string $assetPath = "", string $fileExtension = "")
$app->register('view', Engine::class, [
__DIR__, // publicPath - Root-Verzeichnis (wo index.php liegt)
'views', // skinPath - Template-Pfad (unterstützt relativ/absolut)
'assets', // assetPath - Pfad für kompilierte Assets (unterstützt relativ/absolut)
'.php' // fileExtension - Dateierweiterung für Templates
]);
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
Pfadkonfiguration
CommentTemplate bietet intelligente Pfadbehandlung für sowohl relative als auch absolute Pfade:
Public Path
Der Public Path ist das Root-Verzeichnis Ihrer Web-Anwendung, typischerweise wo index.php liegt. Dies ist das Dokument-Root, aus dem Webserver Dateien ausliefern.
// Beispiel: Wenn Ihre index.php bei /var/www/html/myapp/index.php liegt
$template->setPublicPath('/var/www/html/myapp'); // Root-Verzeichnis
// Windows-Beispiel: Wenn Ihre index.php bei C:\xampp\htdocs\myapp\index.php liegt
$template->setPublicPath('C:\\xampp\\htdocs\\myapp');
Konfiguration des Templates-Pfads
Der Templates-Pfad unterstützt sowohl relative als auch absolute Pfade:
$template = new Engine();
$template->setPublicPath('/var/www/html/myapp'); // Root-Verzeichnis (wo index.php liegt)
// Relative Pfade - werden automatisch mit dem Public Path kombiniert
$template->setSkinPath('views'); // → /var/www/html/myapp/views/
$template->setSkinPath('templates/pages'); // → /var/www/html/myapp/templates/pages/
// Absolute Pfade - werden so verwendet (Unix/Linux)
$template->setSkinPath('/var/www/templates'); // → /var/www/templates/
$template->setSkinPath('/full/path/to/templates'); // → /full/path/to/templates/
// Windows absolute Pfade
$template->setSkinPath('C:\\www\\templates'); // → C:\www\templates\
$template->setSkinPath('D:/projects/templates'); // → D:/projects/templates/
// UNC-Pfade (Windows-Netzwerkfreigaben)
$template->setSkinPath('\\\\server\\share\\templates'); // → \\server\share\templates\
Konfiguration des Asset-Pfads
Der Asset-Pfad unterstützt ebenfalls sowohl relative als auch absolute Pfade:
// Relative Pfade - werden automatisch mit dem Public Path kombiniert
$template->setAssetPath('assets'); // → /var/www/html/myapp/assets/
$template->setAssetPath('static/files'); // → /var/www/html/myapp/static/files/
// Absolute Pfade - werden so verwendet (Unix/Linux)
$template->setAssetPath('/var/www/cdn'); // → /var/www/cdn/
$template->setAssetPath('/full/path/to/assets'); // → /full/path/to/assets/
// Windows absolute Pfade
$template->setAssetPath('C:\\www\\static'); // → C:\www\static\
$template->setAssetPath('D:/projects/assets'); // → D:/projects/assets/
// UNC-Pfade (Windows-Netzwerkfreigaben)
$template->setAssetPath('\\\\server\\share\\assets'); // → \\server\share\assets\
Intelligente Pfaderkennung:
- Relative Pfade: Keine führenden Trennzeichen (
/,\) oder Laufwerksbuchstaben - Unix Absolut: Beginnt mit
/(z. B./var/www/assets) - Windows Absolut: Beginnt mit Laufwerksbuchstabe (z. B.
C:\www,D:/assets) - UNC-Pfade: Beginnt mit
\\(z. B.\\server\share)
So funktioniert es:
- Alle Pfade werden automatisch basierend auf dem Typ (relativ vs. absolut) aufgelöst
- Relative Pfade werden mit dem Public Path kombiniert
@cssund@jserstellen minimierte Dateien in:{resolvedAssetPath}/css/oder{resolvedAssetPath}/js/@assetkopiert einzelne Dateien nach:{resolvedAssetPath}/{relativePath}@assetDirkopiert Verzeichnisse nach:{resolvedAssetPath}/{relativePath}- Intelligentes Caching: Dateien werden nur kopiert, wenn die Quelle neuer als das Ziel ist
Tracy Debugger Integration
CommentTemplate beinhaltet Integration mit Tracy Debugger für Entwicklungs-Logging und Debugging.

Installation
composer require tracy/tracy
Verwendung
<?php
use KnifeLemon\CommentTemplate\Engine;
use Tracy\Debugger;
// Tracy aktivieren (muss vor jeder Ausgabe aufgerufen werden)
Debugger::enable(Debugger::DEVELOPMENT);
Flight::set('flight.content_length', false);
// Template-Überschreibung
$app->register('view', Engine::class, [], function (Engine $builder) use ($app) {
$builder->setPublicPath($app->get('flight.views.topPath'));
$builder->setAssetPath($app->get('flight.views.assetPath'));
$builder->setSkinPath($app->get('flight.views.path'));
$builder->setFileExtension($app->get('flight.views.extension'));
});
$app->map('render', function(string $template, array $data) use ($app): void {
echo $app->view()->render($template, $data);
});
$app->start();
Debug-Panel-Funktionen
CommentTemplate fügt Tracys Debug-Leiste ein benutzerdefiniertes Panel mit vier Tabs hinzu:
- Overview: Konfiguration, Leistungsmetriken und Zähler
- Assets: CSS/JS-Kompilierungsdetails mit Kompressionsraten
- Variables: Original- und transformierte Werte mit angewendeten Filtern
- Timeline: Chronologische Ansicht aller Template-Operationen
Was wird protokolliert
- Template-Rendering (Start/Ende, Dauer, Layouts, Imports)
- Asset-Kompilierung (CSS/JS-Dateien, Größen, Kompressionsraten)
- Variablenverarbeitung (Original/transformierte Werte, Filter)
- Asset-Operationen (Base64-Kodierung, Dateikopieren)
- Leistungsmetriken (Dauer, Speicherverbrauch)
Hinweis: Keine Leistungseinbußen, wenn Tracy nicht installiert oder deaktiviert ist.
Siehe vollständiges funktionierendes Beispiel mit Flight PHP.
Template-Direktiven
Layout-Vererbung
Verwenden Sie Layouts, um eine gemeinsame Struktur zu erstellen:
layout/global_layout.php:
<!DOCTYPE html>
<html>
<head>
<title>{$title}</title>
</head>
<body>
<!--@contents-->
</body>
</html>
view/page.php:
<!--@layout(layout/global_layout)-->
<h1>{$title}</h1>
<p>{$content}</p>
Asset-Verwaltung
CSS-Dateien
<!--@css(/css/styles.css)--> <!-- Minifiziert und gecacht -->
<!--@cssSingle(/css/critical.css)--> <!-- Einzelne Datei, nicht minifiziert -->
JavaScript-Dateien
CommentTemplate unterstützt verschiedene JavaScript-Lade-Strategien:
<!--@js(/js/script.js)--> <!-- Minifiziert, geladen am Ende -->
<!--@jsAsync(/js/analytics.js)--> <!-- Minifiziert, geladen am Ende mit async -->
<!--@jsDefer(/js/utils.js)--> <!-- Minifiziert, geladen am Ende mit defer -->
<!--@jsTop(/js/critical.js)--> <!-- Minifiziert, geladen im Head -->
<!--@jsTopAsync(/js/tracking.js)--> <!-- Minifiziert, geladen im Head mit async -->
<!--@jsTopDefer(/js/polyfill.js)--> <!-- Minifiziert, geladen im Head mit defer -->
<!--@jsSingle(/js/widget.js)--> <!-- Einzelne Datei, nicht minifiziert -->
<!--@jsSingleAsync(/js/ads.js)--> <!-- Einzelne Datei, nicht minifiziert, async -->
<!--@jsSingleDefer(/js/social.js)--> <!-- Einzelne Datei, nicht minifiziert, defer -->
Asset-Direktiven in CSS/JS-Dateien
CommentTemplate verarbeitet auch Asset-Direktiven innerhalb von CSS- und JavaScript-Dateien während der Kompilierung:
CSS-Beispiel:
/* In Ihren CSS-Dateien */
@font-face {
font-family: 'CustomFont';
src: url('<!--@asset(fonts/custom.woff2)-->') format('woff2');
}
.background-image {
background: url('<!--@asset(images/bg.jpg)-->');
}
.inline-icon {
background: url('<!--@base64(icons/star.svg)-->');
}
JavaScript-Beispiel:
/* In Ihren JS-Dateien */
const fontUrl = '<!--@asset(fonts/custom.woff2)-->';
const imageData = '<!--@base64(images/icon.png)-->';
Base64-Kodierung
<!--@base64(images/logo.png)--> <!-- Inline als Data-URI -->
Beispiel:
<!-- Kleine Bilder inline als Data-URIs für schnelleres Laden -->
<img src="<!--@base64(images/logo.png)-->" alt="Logo">
<div style="background-image: url('<!--@base64(icons/star.svg)-->');">
Kleines Icon als Hintergrund
</div>
Asset-Kopieren
<!--@asset(images/photo.jpg)--> <!-- Kopiert einzelnes Asset in das Public-Verzeichnis -->
<!--@assetDir(assets)--> <!-- Kopiert gesamtes Verzeichnis in das Public-Verzeichnis -->
Beispiel:
<!-- Statische Assets kopieren und referenzieren -->
<img src="<!--@asset(images/hero-banner.jpg)-->" alt="Hero Banner">
<a href="<!--@asset(documents/brochure.pdf)-->" download>Download Brochure</a>
<!-- Gesamtes Verzeichnis kopieren (Fonts, Icons usw.) -->
<!--@assetDir(assets/fonts)-->
<!--@assetDir(assets/icons)-->
Template-Einbindungen
<!--@import(components/header)--> <!-- Andere Templates einbinden -->
Beispiel:
<!-- Wiederverwendbare Komponenten einbinden -->
<!--@import(components/header)-->
<main>
<h1>Willkommen auf unserer Website</h1>
<!--@import(components/sidebar)-->
<div class="content">
<p>Hauptinhalt hier...</p>
</div>
</main>
<!--@import(components/footer)-->
Variablenverarbeitung
Grundlegende Variablen
<h1>{$title}</h1>
<p>{$description}</p>
Variablenfilter
{$title|upper} <!-- In Großbuchstaben umwandeln -->
{$content|lower} <!-- In Kleinbuchstaben umwandeln -->
{$html|striptag} <!-- HTML-Tags entfernen -->
{$text|escape} <!-- HTML escapen -->
{$multiline|nl2br} <!-- Zeilenumbrüche in <br> umwandeln -->
{$html|br2nl} <!-- <br>-Tags in Zeilenumbrüche umwandeln -->
{$description|trim} <!-- Leerzeichen kürzen -->
{$subject|title} <!-- In Titel-Großschreibung umwandeln -->
Variablenbefehle
{$title|default=Default Title} <!-- Standardwert setzen -->
{$name|concat= (Admin)} <!-- Text konkatenerieren -->
Variablenbefehle
{$content|striptag|trim|escape} <!-- Mehrere Filter verketten -->
Kommentare
Template-Kommentare werden vollständig aus dem Output entfernt und erscheinen nicht im finalen HTML:
{* Dies ist ein einzeiliger Template-Kommentar *}
{*
Dies ist ein mehrzeiliger
Template-Kommentar
der mehrere Zeilen umfasst
*}
<h1>{$title}</h1>
{* Debug-Kommentar: Überprüfen, ob die Title-Variable funktioniert *}
<p>{$content}</p>
Hinweis: Template-Kommentare {* ... *} unterscheiden sich von HTML-Kommentaren <!-- ... -->. Template-Kommentare werden während der Verarbeitung entfernt und erreichen nie den Browser.
Beispiel-Projektstruktur
project/
├── source/
│ ├── layouts/
│ │ └── default.php
│ ├── components/
│ │ ├── header.php
│ │ └── footer.php
│ ├── css/
│ │ ├── bootstrap.min.css
│ │ └── custom.css
│ ├── js/
│ │ ├── app.js
│ │ └── bootstrap.min.js
│ └── homepage.php
├── public/
│ └── assets/ # Generierte Assets
│ ├── css/
│ └── js/
└── vendor/Awesome-plugins/easy_query
EasyQuery
knifelemon/easy-query ist ein leichtgewichtiger, fließender SQL-Query-Builder, der SQL und Parameter für vorbereitete Anweisungen generiert. Funktioniert mit SimplePdo.
Features
- 🔗 Fließende API - Methoden verkettet für lesbare Abfragekonstruktion
- 🛡️ SQL-Injection-Schutz - Automatische Parameterbindung mit vorbereiteten Anweisungen
- 🔧 Raw-SQL-Unterstützung - Rohe SQL-Ausdrücke mit
raw()einfügen - 📝 Mehrere Abfragetypen - SELECT, INSERT, UPDATE, DELETE, COUNT
- 🔀 JOIN-Unterstützung - INNER, LEFT, RIGHT Joins mit Aliasen
- 🎯 Erweiterte Bedingungen - LIKE, IN, NOT IN, BETWEEN, Vergleichsoperatoren
- 🌐 Datenbankunabhängig - Gibt SQL + Parameter zurück, verwendbar mit jeder DB-Verbindung
- 🪶 Leichtgewichtig - Minimaler Footprint ohne Abhängigkeiten
Installation
composer require knifelemon/easy-query
Quick Start
use KnifeLemon\EasyQuery\Builder;
$q = Builder::table('users')
->select(['id', 'name', 'email'])
->where(['status' => 'active'])
->orderBy('created_at DESC')
->limit(10)
->build();
// Verwenden mit Flights SimplePdo
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
Understanding build()
Die Methode build() gibt ein Array mit sql und params zurück. Diese Trennung hält Ihre Datenbank sicher durch die Verwendung vorbereiteter Anweisungen.
$q = Builder::table('users')
->where(['email' => 'user@example.com'])
->build();
// Gibt zurück:
// [
// 'sql' => 'SELECT * FROM users WHERE email = ?',
// 'params' => ['user@example.com']
// ]
Query Types
SELECT
// Alle Spalten auswählen
$q = Builder::table('users')->build();
// SELECT * FROM users
// Spezifische Spalten auswählen
$q = Builder::table('users')
->select(['id', 'name', 'email'])
->build();
// SELECT id, name, email FROM users
// Mit Tabellenalias
$q = Builder::table('users')
->alias('u')
->select(['u.id', 'u.name'])
->build();
// SELECT u.id, u.name FROM users AS u
INSERT
$q = Builder::table('users')
->insert([
'name' => 'John Doe',
'email' => 'john@example.com',
'status' => 'active'
])
->build();
// INSERT INTO users SET name = ?, email = ?, status = ?
Flight::db()->runQuery($q['sql'], $q['params']);
$userId = Flight::db()->lastInsertId();
UPDATE
$q = Builder::table('users')
->update(['status' => 'inactive', 'updated_at' => date('Y-m-d H:i:s')])
->where(['id' => 123])
->build();
// UPDATE users SET status = ?, updated_at = ? WHERE id = ?
Flight::db()->runQuery($q['sql'], $q['params']);
DELETE
$q = Builder::table('users')
->delete()
->where(['id' => 123])
->build();
// DELETE FROM users WHERE id = ?
Flight::db()->runQuery($q['sql'], $q['params']);
COUNT
$q = Builder::table('users')
->count()
->where(['status' => 'active'])
->build();
// SELECT COUNT(*) AS cnt FROM users WHERE status = ?
$count = Flight::db()->fetchField($q['sql'], $q['params']);
WHERE Conditions
Simple Equality
$q = Builder::table('users')
->where(['id' => 123, 'status' => 'active'])
->build();
// WHERE id = ? AND status = ?
Comparison Operators
$q = Builder::table('users')
->where([
'age' => ['>=', 18],
'score' => ['<', 100],
'name' => ['!=', 'admin']
])
->build();
// WHERE age >= ? AND score < ? AND name != ?
LIKE
$q = Builder::table('users')
->where(['name' => ['LIKE', '%john%']])
->build();
// WHERE name LIKE ?
IN / NOT IN
// IN
$q = Builder::table('users')
->where(['id' => ['IN', [1, 2, 3, 4, 5]]])
->build();
// WHERE id IN (?, ?, ?, ?, ?)
// NOT IN
$q = Builder::table('users')
->where(['status' => ['NOT IN', ['banned', 'deleted']]])
->build();
// WHERE status NOT IN (?, ?)
BETWEEN
$q = Builder::table('products')
->where(['price' => ['BETWEEN', [100, 500]]])
->build();
// WHERE price BETWEEN ? AND ?
OR Conditions
Verwenden Sie orWhere(), um OR-gruppierte Bedingungen hinzuzufügen:
$q = Builder::table('users')
->where(['status' => 'active'])
->orWhere([
'role' => 'admin',
'permissions' => ['LIKE', '%manage%']
])
->build();
// WHERE status = ? AND (role = ? OR permissions LIKE ?)
JOIN
INNER JOIN
$q = Builder::table('users')
->alias('u')
->select(['u.id', 'u.name', 'p.title'])
->innerJoin('posts', 'u.id = p.user_id', 'p')
->build();
// SELECT u.id, u.name, p.title FROM users AS u INNER JOIN posts AS p ON u.id = p.user_id
LEFT JOIN
$q = Builder::table('users')
->alias('u')
->select(['u.name', 'o.total'])
->leftJoin('orders', 'u.id = o.user_id', 'o')
->build();
// ... LEFT JOIN orders AS o ON u.id = o.user_id
Multiple JOINs
$q = Builder::table('orders')
->alias('o')
->select(['o.id', 'u.name AS customer', 'p.title AS product'])
->innerJoin('users', 'o.user_id = u.id', 'u')
->leftJoin('order_items', 'o.id = oi.order_id', 'oi')
->leftJoin('products', 'oi.product_id = p.id', 'p')
->where(['o.status' => 'completed'])
->build();
Ordering, Grouping, and Limits
ORDER BY
$q = Builder::table('users')
->orderBy('created_at DESC')
->build();
// ORDER BY created_at DESC
GROUP BY
$q = Builder::table('orders')
->select(['user_id', 'COUNT(*) as order_count'])
->groupBy('user_id')
->build();
// SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id
LIMIT and OFFSET
$q = Builder::table('users')
->limit(10)
->build();
// LIMIT 10
$q = Builder::table('users')
->limit(10, 20) // limit, offset
->build();
// LIMIT 10 OFFSET 20
Raw SQL Expressions
Verwenden Sie raw(), wenn Sie SQL-Funktionen oder Ausdrücke benötigen, die nicht als gebundene Parameter behandelt werden sollen.
Basic Raw
$q = Builder::table('users')
->update([
'login_count' => Builder::raw('login_count + 1'),
'updated_at' => Builder::raw('NOW()')
])
->where(['id' => 123])
->build();
// SET login_count = login_count + 1, updated_at = NOW()
Raw with Bound Parameters
$q = Builder::table('orders')
->update([
'total' => Builder::raw('COALESCE(subtotal, ?) + ?', [0, 10])
])
->where(['id' => 1])
->build();
// SET total = COALESCE(subtotal, ?) + ?
// params: [0, 10, 1]
Raw in WHERE (Subquery)
$q = Builder::table('products')
->where([
'price' => ['>', Builder::raw('(SELECT AVG(price) FROM products)')]
])
->build();
// WHERE price > (SELECT AVG(price) FROM products)
Safe Identifiers for User Input
Wenn Spaltennamen aus Benutzereingaben stammen, verwenden Sie safeIdentifier(), um SQL-Injection zu verhindern:
$sortColumn = $_GET['sort']; // z.B. 'created_at'
$safeColumn = Builder::safeIdentifier($sortColumn);
$q = Builder::table('users')
->orderBy($safeColumn . ' DESC')
->build();
// Wenn Benutzer versucht: "name; DROP TABLE users--"
// Wirft InvalidArgumentException
rawSafe for User-Provided Column Names
$userColumn = $_GET['aggregate_column'];
$q = Builder::table('orders')
->select([
Builder::rawSafe('SUM({col})', ['col' => $userColumn])->value . ' AS total'
])
->build();
// Validiert Spaltennamen, wirft Ausnahme bei ungültig
Warnung: Konkatenieren Sie Benutzereingaben nie direkt in
raw(). Verwenden Sie immer gebundene Parameter odersafeIdentifier().
Query Builder Reuse
Clear Methods
Löschen Sie spezifische Teile, um den Builder wiederzuverwenden:
$query = Builder::table('users')
->select(['id', 'name'])
->where(['status' => 'active'])
->orderBy('created_at DESC');
// Erste Abfrage
$q1 = $query->limit(10)->build();
// Löschen und wiederverwenden
$query->clearWhere()->clearLimit();
// Zweite Abfrage mit anderen Bedingungen
$q2 = $query
->where(['status' => 'pending'])
->limit(5)
->build();
Available Clear Methods
| Method | Description |
|---|---|
clearWhere() |
WHERE-Bedingungen und Parameter löschen |
clearSelect() |
SELECT-Spalten auf Standard '*' zurücksetzen |
clearJoin() |
Alle JOIN-Klauseln löschen |
clearGroupBy() |
GROUP BY-Klausel löschen |
clearOrderBy() |
ORDER BY-Klausel löschen |
clearLimit() |
LIMIT und OFFSET löschen |
clearAll() |
Builder auf Anfangszustand zurücksetzen |
Pagination Example
$baseQuery = Builder::table('users')
->select(['id', 'name', 'email'])
->where(['status' => 'active'])
->orderBy('created_at DESC');
// Gesamtzahl abrufen
$countQuery = clone $baseQuery;
$countResult = $countQuery->clearSelect()->count()->build();
$total = Flight::db()->fetchField($countResult['sql'], $countResult['params']);
// Paginierte Ergebnisse abrufen
$page = 1;
$perPage = 20;
$listResult = $baseQuery->limit($perPage, ($page - 1) * $perPage)->build();
$users = Flight::db()->fetchAll($listResult['sql'], $listResult['params']);
Dynamic Query Building
$query = Builder::table('products')->alias('p');
if (!empty($categoryId)) {
$query->where(['p.category_id' => $categoryId]);
}
if (!empty($minPrice)) {
$query->where(['p.price' => ['>=', $minPrice]]);
}
if (!empty($maxPrice)) {
$query->where(['p.price' => ['<=', $maxPrice]]);
}
if (!empty($searchTerm)) {
$query->where(['p.name' => ['LIKE', "%{$searchTerm}%"]]);
}
$result = $query->orderBy('p.created_at DESC')->limit(20)->build();
$products = Flight::db()->fetchAll($result['sql'], $result['params']);
Full FlightPHP Example
use KnifeLemon\EasyQuery\Builder;
// Benutzer mit Pagination auflisten
Flight::route('GET /users', function() {
$page = (int) (Flight::request()->query['page'] ?? 1);
$perPage = 20;
$q = Builder::table('users')
->select(['id', 'name', 'email', 'created_at'])
->where(['status' => 'active'])
->orderBy('created_at DESC')
->limit($perPage, ($page - 1) * $perPage)
->build();
$users = Flight::db()->fetchAll($q['sql'], $q['params']);
Flight::json(['users' => $users, 'page' => $page]);
});
// Benutzer erstellen
Flight::route('POST /users', function() {
$data = Flight::request()->data;
$q = Builder::table('users')
->insert([
'name' => $data->name,
'email' => $data->email,
'created_at' => Builder::raw('NOW()')
])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['id' => Flight::db()->lastInsertId()]);
});
// Benutzer aktualisieren
Flight::route('PUT /users/@id', function($id) {
$data = Flight::request()->data;
$q = Builder::table('users')
->update([
'name' => $data->name,
'email' => $data->email,
'updated_at' => Builder::raw('NOW()')
])
->where(['id' => $id])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['success' => true]);
});
// Benutzer löschen
Flight::route('DELETE /users/@id', function($id) {
$q = Builder::table('users')
->delete()
->where(['id' => $id])
->build();
Flight::db()->runQuery($q['sql'], $q['params']);
Flight::json(['success' => true]);
});
API Reference
Static Methods
| Method | Description |
|---|---|
Builder::table(string $table) |
Neue Builder-Instanz für die Tabelle erstellen |
Builder::raw(string $sql, array $bindings = []) |
Roher SQL-Ausdruck erstellen |
Builder::rawSafe(string $expr, array $identifiers, array $bindings = []) |
Roher Ausdruck mit sicherer Identifier-Substitution |
Builder::safeIdentifier(string $identifier) |
Identifier validieren und sicheren Spalten-/Tabellennamen zurückgeben |
Instance Methods
| Method | Description |
|---|---|
alias(string $alias) |
Tabellenalias setzen |
select(string\|array $columns) |
Spalten zum Auswählen setzen (Standard: '*') |
where(array $conditions) |
WHERE-Bedingungen hinzufügen (AND) |
orWhere(array $conditions) |
OR-WHERE-Bedingungen hinzufügen |
join(string $table, string $condition, string $alias, string $type) |
JOIN-Klausel hinzufügen |
innerJoin(string $table, string $condition, string $alias) |
INNER JOIN hinzufügen |
leftJoin(string $table, string $condition, string $alias) |
LEFT JOIN hinzufügen |
groupBy(string $groupBy) |
GROUP BY-Klausel hinzufügen |
orderBy(string $orderBy) |
ORDER BY-Klausel hinzufügen |
limit(int $limit, int $offset = 0) |
LIMIT und OFFSET hinzufügen |
count(string $column = '*') |
Abfrage auf COUNT setzen |
insert(array $data) |
Abfrage auf INSERT setzen |
update(array $data) |
Abfrage auf UPDATE setzen |
delete() |
Abfrage auf DELETE setzen |
build() |
Bauen und ['sql' => ..., 'params' => ...] zurückgeben |
get() |
Alias für build() |
Tracy Debugger Integration
EasyQuery integriert sich automatisch mit Tracy Debugger, falls installiert. Keine Einrichtung erforderlich!
composer require tracy/tracy
use Tracy\Debugger;
Debugger::enable();
// Alle Abfragen werden automatisch im Tracy-Panel protokolliert
$q = Builder::table('users')->where(['status' => 'active'])->build();
Das Tracy-Panel zeigt:
- Gesamtabfragen und Aufschlüsselung nach Typ
- Generiertes SQL (syntaxhervorgehoben)
- Parameter-Array
- Abfragedetails (Tabelle, where, joins usw.)
Für die vollständige Dokumentation besuchen Sie das GitHub-Repository.
Awesome-plugins/twig
Twig
Twig ist eine flexible, schnelle und sichere Template-Engine für PHP. Es ist die Template-Sprache, die von Symfony und vielen anderen Projekten verwendet wird, was bedeutet, dass KI-Codierungstools und die meisten PHP-Entwickler ihre Syntax bereits gut kennen. Twig kompiliert Templates in optimiertes PHP, maskiert die Ausgabe standardmäßig automatisch (ideal für XSS-Schutz) und lässt sich leicht mit Filtern, Funktionen und Erweiterungen erweitern.
Installation
Mit Composer installieren.
composer require twig/twig
Grundkonfiguration
Es gibt einige grundlegende Konfigurationsoptionen, um zu beginnen. Sie können mehr darüber in der Twig-Dokumentation lesen.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data): void {
$loader = new \Twig\Loader\FilesystemLoader(Flight::get('flight.views.path'));
$twig = new \Twig\Environment($loader, [
// Wo Twig seine kompilierten Templates speichert
'cache' => __DIR__ . '/../cache/twig',
// Templates neu kompilieren, wenn sich die Quelle ändert (praktisch in der Entwicklung)
'auto_reload' => true,
]);
echo $twig->render($template, $data);
});
Twig als View-Klasse registrieren
Wenn Sie eine einzelne Twig-Umgebung wiederverwenden möchten (für die Produktion empfohlen), registrieren Sie diese und verweisen Sie render darauf:
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('view', \Twig\Environment::class, [
new \Twig\Loader\FilesystemLoader($app->get('flight.views.path')),
[
'cache' => __DIR__ . '/../cache/twig',
'auto_reload' => true,
],
]);
$app->map('render', function(string $template, array $data): void {
echo Flight::view()->render($template, $data);
});
Einfaches Layout-Beispiel
Hier ist ein einfaches Beispiel für eine Layout-Datei. Dies ist die Datei, die verwendet wird, um alle anderen Views einzubetten.
{# app/views/layout.twig #}
<!doctype html>
<html lang="en">
<head>
<title>{% if title %}{{ title }} - {% endif %}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
{# Ihre Navigationselemente hier #}
</nav>
</header>
<div id="content">
{# Hier passiert die Magie #}
{% block content %}{% endblock %}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
Und nun haben wir Ihre Datei, die im Content-Block gerendert wird:
{# app/views/home.twig #}
{# Dies teilt Twig mit, dass diese Datei "innerhalb" der layout.twig-Datei ist #}
{% extends 'layout.twig' %}
{# Dies ist der Inhalt, der im Layout innerhalb des Content-Blocks gerendert wird #}
{% block content %}
<h1>Startseite</h1>
<p>Willkommen in meiner App!</p>
{% endblock %}
Wenn Sie dies dann in Ihrer Funktion oder Ihrem Controller rendern möchten, würden Sie so etwas tun:
// einfache Route
Flight::route('/', function () {
Flight::render('home.twig', [
'title' => 'Startseite'
]);
});
// oder wenn Sie einen Controller verwenden
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.twig', [
'title' => 'Startseite'
]);
}
}
Weitere Informationen zur vollen Nutzung von Twig finden Sie in der Twig-Dokumentation!
Debugging
Twig enthält eine Debug-Erweiterung, die eine dump()-Funktion hinzufügt, die Sie in Templates verwenden können. Aktivieren Sie sie nur in der Entwicklung:
$app->register('view', \Twig\Environment::class, [
new \Twig\Loader\FilesystemLoader($app->get('flight.views.path')),
[
'cache' => __DIR__ . '/../cache/twig',
'debug' => true, // erforderlich für die dump()-Funktion
'auto_reload' => true,
],
], function (\Twig\Environment $twig): void {
$twig->addExtension(new \Twig\Extension\DebugExtension());
});
Dann in einem Template:
{{ dump(user) }}
Sie können Twig auch mit Tracy für das PHP-Level-Debugging kombinieren. Für Template-Level-Metriken (Renderzeit, Speicher, welche Templates/Blöcke ausgeführt wurden), verwenden Sie das optionale Twig-Panel in flightphp/tracy-extensions: übergeben Sie ein Twig\Profiler\Profile als twig_profile an TracyExtensionLoader. Die optionale TwigTracyExtension stellt {{ dump() }} / {{ bdump() }} / {{ dumpe() }} in Templates zur Verfügung, wenn Tracy aktiviert ist.
Sicherheitshinweis
Twig maskiert die Ausgabe standardmäßig automatisch, was vor XSS-Angriffen schützt. Verwenden Sie bevorzugt {{ variable }} für Text. Verwenden Sie den Filter |raw nur, wenn Sie dem HTML-Inhalt bewusst vertrauen (z. B. bereinigtes Markdown, das Sie bereits serverseitig verarbeitet haben).
Awesome-plugins/session
FlightPHP Session - Leichtgewichtiger Dateibasierter Session-Handler
Dies ist ein leichtgewichtiger, dateibasisierter Session-Handler-Plugin für das Flight PHP Framework. Es bietet eine einfache, aber leistungsstarke Lösung zur Verwaltung von Sessions, mit Funktionen wie nicht blockierendem Lesen von Sessions, optionaler Verschlüsselung, Auto-Commit-Funktionalität und einem Testmodus für die Entwicklung. Session-Daten werden in Dateien gespeichert, was es ideal für Anwendungen macht, die keine Datenbank benötigen.
Falls du eine Datenbank verwenden möchtest, schaue dir das ghostff/session Plugin an, das viele der gleichen Funktionen bietet, aber mit einer Datenbank-Backend.
Besuche das Github-Repository für den vollständigen Quellcode und Details.
Installation
Installiere das Plugin über Composer:
composer require flightphp/session
Grundlegende Nutzung
Hier ist ein einfaches Beispiel, wie du das flightphp/session-Plugin in deiner Flight-Anwendung verwendest:
require 'vendor/autoload.php';
use flight\Session;
$app = Flight::app();
// Registriere den Session-Dienst
$app->register('session', Session::class);
// Beispiel-Route mit Session-Nutzung
Flight::route('/login', function() {
$session = Flight::session();
$session->set('user_id', 123);
$session->set('username', 'johndoe');
$session->set('is_admin', false);
echo $session->get('username'); // Gibt aus: johndoe
echo $session->get('preferences', 'default_theme'); // Gibt aus: default_theme
if ($session->get('user_id')) {
Flight::json(['message' => 'User is logged in!', 'user_id' => $session->get('user_id')]);
}
});
Flight::route('/logout', function() {
$session = Flight::session();
$session->clear(); // Löschung aller Session-Daten
Flight::json(['message' => 'Logged out successfully']);
});
Flight::start();
Wichtige Punkte
- Nicht blockierend: Verwende
read_and_closestandardmäßig, um Probleme mit Session-Sperrungen zu vermeiden. - Auto-Commit: Standardmäßig aktiviert, sodass Änderungen automatisch beim Herunterfahren gespeichert werden, es sei denn, es wird deaktiviert.
- Dateispeicherung: Sessions werden standardmäßig im System-Temp-Verzeichnis unter
/flight_sessionsgespeichert.
Konfiguration
Du kannst den Session-Handler anpassen, indem du ein Array von Optionen beim Registrieren übergeben:
// Ja, es ist ein doppeltes Array :)
$app->register('session', Session::class, [ [
'save_path' => '/custom/path/to/sessions', // Verzeichnis für Session-Dateien
'prefix' => 'myapp_', // Präfix für Session-Dateien
'encryption_key' => 'a-secure-32-byte-key-here', // Verschlüsselung aktivieren (32 Bytes empfohlen für AES-256-CBC)
'auto_commit' => false, // Auto-Commit deaktivieren für manuelle Kontrolle
'start_session' => true, // Session automatisch starten (Standard: true)
'test_mode' => false, // Testmodus für die Entwicklung aktivieren
'serialization' => 'json', // Serialisierungs-Methode: 'json' (Standard) oder 'php' (Legacy)
] ]);
Konfigurationsoptionen
| Option | Beschreibung | Standardwert |
|---|---|---|
save_path |
Verzeichnis, in dem Session-Dateien gespeichert werden | sys_get_temp_dir() . '/flight_sessions' |
prefix |
Präfix für die gespeicherte Session-Datei | sess_ |
encryption_key |
Schlüssel für AES-256-CBC-Verschlüsselung (optional) | null (keine Verschlüsselung) |
auto_commit |
Automatische Speicherung von Session-Daten beim Herunterfahren | true |
start_session |
Session automatisch starten | true |
test_mode |
Im Testmodus ausführen, ohne PHP-Sessions zu beeinflussen | false |
test_session_id |
Benutzerdefinierte Session-ID für den Testmodus (optional) | Zufällig generiert, wenn nicht gesetzt |
serialization |
Serialisierungs-Methode: 'json' (Standard, sicher) oder 'php' (Legacy, erlaubt Objekte) | 'json' |
Serialisierungsmodi
Standardmäßig verwendet diese Bibliothek JSON-Serialisierung für Session-Daten, was sicher ist und PHP-Objekt-Injektions-Schwachstellen verhindert. Wenn du PHP-Objekte in der Session speichern musst (nicht empfohlen für die meisten Apps), kannst du auf die Legacy-PHP-Serialisierung umschalten:
'serialization' => 'json'(Standard):- Nur Arrays und Primitive sind in Session-Daten erlaubt.
- Sicherer: Immun gegen PHP-Objekt-Injection.
- Dateien werden mit
J(einfaches JSON) oderF(verschlüsseltes JSON) präfixiert.
'serialization' => 'php':- Erlaubt das Speichern von PHP-Objekten (mit Vorsicht verwenden).
- Dateien werden mit
P(einfaches PHP-Serialisieren) oderE(verschlüsseltes PHP-Serialisieren) präfixiert.
Hinweis: Wenn du JSON-Serialisierung verwendest, wirft das Versuch, ein Objekt zu speichern, eine Ausnahme.
Erweiterte Nutzung
Manuelles Commit
Wenn du Auto-Commit deaktivierst, musst du Änderungen manuell speichern:
$app->register('session', Session::class, ['auto_commit' => false]);
Flight::route('/update', function() {
$session = Flight::session();
$session->set('key', 'value');
$session->commit(); // Änderungen explizit speichern
});
Session-Sicherheit mit Verschlüsselung
Aktiviere Verschlüsselung für sensible Daten:
$app->register('session', Session::class, [
'encryption_key' => 'your-32-byte-secret-key-here'
]);
Flight::route('/secure', function() {
$session = Flight::session();
$session->set('credit_card', '4111-1111-1111-1111'); // Wird automatisch verschlüsselt
echo $session->get('credit_card'); // Wird beim Abruf entschlüsselt
});
Session-Regeneration
Regeneriere die Session-ID für Sicherheit (z. B. nach dem Login):
Flight::route('/post-login', function() {
$session = Flight::session();
$session->regenerate(); // Neue ID, Daten behalten
// ODER
$session->regenerate(true); // Neue ID, alte Daten löschen
});
Middleware-Beispiel
Schütze Routen mit sessionbasierter Authentifizierung:
Flight::route('/admin', function() {
Flight::json(['message' => 'Welcome to the admin panel']);
})->addMiddleware(function() {
$session = Flight::session();
if (!$session->get('is_admin')) {
Flight::halt(403, 'Access denied');
}
});
Dies ist nur ein einfaches Beispiel für die Nutzung in Middleware. Für ein detaillierteres Beispiel, siehe die Middleware-Dokumentation.
Methoden
Die Session-Klasse bietet diese Methoden:
set(string $key, $value): Speichert einen Wert in der Session.get(string $key, $default = null): Ruft einen Wert ab, mit einem optionalen Standardwert, falls der Schlüssel nicht existiert.delete(string $key): Entfernt einen bestimmten Schlüssel aus der Session.clear(): Löscht alle Session-Daten, behält aber den gleichen Dateinamen für die Session.commit(): Speichert die aktuellen Session-Daten im Dateisystem.id(): Gibt die aktuelle Session-ID zurück.regenerate(bool $deleteOldFile = false): Regeneriert die Session-ID inklusive Erstellen einer neuen Session-Datei, behält alle alten Daten und die alte Datei bleibt im System. Wenn$deleteOldFiletrueist, wird die alte Session-Datei gelöscht.destroy(string $id): Zerstört eine Session anhand der ID und löscht die Session-Datei aus dem System. Dies ist Teil desSessionHandlerInterfaceund$idist erforderlich. Typische Nutzung wäre$session->destroy($session->id()).getAll(): Gibt alle Daten der aktuellen Session zurück.
Alle Methoden außer get() und id() geben die Session-Instanz für Kettenaufrufe zurück.
Warum dieses Plugin verwenden?
- Leichtgewichtig: Keine externen Abhängigkeiten – nur Dateien.
- Nicht blockierend: Vermeidet Session-Sperrungen mit
read_and_closestandardmäßig. - Sicher: Unterstützt AES-256-CBC-Verschlüsselung für sensible Daten.
- Flexibel: Optionen für Auto-Commit, Testmodus und manuelle Kontrolle.
- Flight-Native: Speziell für das Flight-Framework entwickelt.
Technische Details
- Speicherformat: Session-Dateien werden mit
sess_präfixiert und im konfiguriertensave_pathgespeichert. Dateiinhalts-Präfixe:J: Einfaches JSON (Standard, keine Verschlüsselung)F: Verschlüsseltes JSON (Standard mit Verschlüsselung)P: Einfaches PHP-Serialisieren (Legacy, keine Verschlüsselung)E: Verschlüsseltes PHP-Serialisieren (Legacy mit Verschlüsselung)
- Verschlüsselung: Verwende AES-256-CBC mit einem zufälligen IV pro Session-Schreibvorgang, wenn ein
encryption_keyangegeben ist. Verschlüsselung funktioniert für beide JSON- und PHP-Serialisierungsmodi. - Serialisierung: JSON ist die Standard- und sicherste Methode. PHP-Serialisierung ist für Legacy/fortgeschrittene Nutzung verfügbar, ist aber weniger sicher.
- Garbage Collection: Implementiert PHP’s
SessionHandlerInterface::gc(), um abgelaufene Sessions zu bereinigen.
Beitrag
Beiträge sind willkommen! Forke das Repository, mache deine Änderungen und reiche einen Pull-Request ein. Melde Fehler oder schlage Features über den Github-Issue-Tracker vor.
Lizenz
Dieses Plugin ist unter der MIT-Lizenz lizenziert. Siehe das Github-Repository für Details.
Awesome-plugins/runway
Runway
Runway ist eine CLI-Anwendung, die Ihnen hilft, Ihre Flight-Anwendungen zu verwalten. Sie kann Controller generieren, alle Routen anzeigen, KI-Setup-Helfer ausführen, Migrationen (im Skeleton) und mehr. Sie basiert auf der ausgezeichneten adhocore/php-cli Bibliothek.
Klicken Sie hier, um den Code anzuzeigen.
Scaffolding-Befehle sind absichtlich mit dem offiziellen Skeleton abgestimmt, damit KI-Coding-Tools und Menschen jedes Mal die gleichen Pfade, Namespaces und Constructor-Injection-Stile erhalten.
Installation
Mit Composer installieren.
composer require flightphp/runway
Das Skeleton hängt bereits von Runway ab; verwenden Sie php runway aus dem Projekt-Root.
Grundlegende Konfiguration
Beim ersten Ausführen von Runway wird versucht, eine runway-Konfiguration in app/config/config.php über den Schlüssel 'runway' zu finden.
<?php
// app/config/config.php
return [
'runway' => [
'app_root' => 'app/',
'public_root' => 'public/',
// optional; das Skeleton verwendet auch index_root für den öffentlichen Einstieg
'index_root' => 'public/index.php',
],
];
HINWEIS - Ab v1.2.0 ist
.runway-config.jsonzugunsten vonapp/config/config.phpveraltet. Migrieren Sie mitphp runway config:migratebeim Upgrade älterer Projekte. Das Skeleton kann beim Erstellen eines Projekts weiterhin eine kleine.runway-config.jsonschreiben, um die Kompatibilität zu gewährleisten; bevorzugen Sie denrunway-Schlüssel inconfig.phpfür die Zukunft.
Projektrouterkennung
Runway ist intelligent genug, um das Root-Verzeichnis Ihres Projekts zu erkennen, auch wenn Sie es aus einem Unterverzeichnis ausführen. Es sucht nach Indikatoren wie composer.json, .git oder app/config/config.php, um zu bestimmen, wo sich das Projekt-Root befindet. Das bedeutet, dass Sie Runway-Befehle von überall in Ihrem Projekt ausführen können!
Verwendung
Runway verfügt über eine Reihe von Befehlen, die Sie zur Verwaltung Ihrer Flight-Anwendung verwenden können. Es gibt zwei einfache Möglichkeiten, Runway zu verwenden.
- Wenn Sie das Skeleton-Projekt verwenden, können Sie
php runway [Befehl]aus dem Root Ihres Projekts ausführen. - Wenn Sie Runway als über Composer installiertes Paket verwenden, können Sie
vendor/bin/runway [Befehl]aus dem Root Ihres Projekts ausführen.
Befehlsliste
Sie können eine Liste aller verfügbaren Befehle anzeigen, indem Sie den Befehl php runway ausführen.
php runway
Verlassen Sie sich nur auf Befehle, die tatsächlich in dieser Liste für Ihre Installation erscheinen (Kern-Runway-Befehle vs. projektspezifische Befehle wie migrate des Skeletons).
Befehlshilfe
Für jeden Befehl können Sie das Flag --help übergeben, um weitere Informationen zur Verwendung des Befehls zu erhalten.
php runway routes --help
php runway make:controller --help
Hier sind einige Beispiele:
Controller generieren
make:controller erstellt ein Scaffold für einen Controller, der mit dem offiziellen Skeleton-Layout übereinstimmt:
| Pfad | app/Controller/{Name}.php |
| Namespace | App\Controller |
| Stil | Constructor-Injection von flight\Engine (kein Flight:: im Klassenrumpf) |
php runway make:controller MyController
# → app/Controller/MyController.php
# namespace App\Controller;
Beispiel der erwarteten Form (vereinfacht):
<?php
declare(strict_types=1);
namespace App\Controller;
use flight\Engine;
class MyController
{
protected Engine $app;
public function __construct(Engine $app)
{
$this->app = $app;
}
public function index(): void
{
// z.B. $this->app->render('…', […]);
}
}
Registrieren Sie es mit einem Klassen-Callable, damit Dice den Controller erstellen kann:
// app/config/routes.php
use App\Controller\MyController;
$router->get('/mine', [MyController::class, 'index']);
Warum dieses Layout? Die Ordnergroß-/Kleinschreibung muss mit dem Namespace übereinstimmen (Controller nicht controllers) für Composer PSR-4 unter Linux—siehe Autoloading. Der gleiche Pfad ist es, den Root- und Scoped-AGENTS.md-Dateien KI-Tools mitteilen, sie zu verwenden, damit generierte und handgeschriebene Controller identisch bleiben.
Ältere Dokumentationen und Community-Projekte verwendeten manchmal
app/controllers/undapp\controllers. Das bleibt gültig, wenn Ihr Baum weiterhin Kleinbuchstaben für Ordner verwendet. Neue Skeleton-Projekte und aktuellemake:controller-Ausgaben verwendenapp/Controller/+App\Controller.
Active Record Modell generieren
Stellen Sie zuerst sicher, dass Sie das Active Record Plugin installiert haben.
php runway make:record users
Im offiziellen Skeleton leben Modelle unter app/Model/ mit dem Namespace App\Model, und die DB-Verbindung ist SimplePdo (injizieren Sie es oder übergeben Sie es an den ActiveRecord-Konstruktor). Generierte Dateinamen/Namespaces folgen den aktuellen Standardeinstellungen von Runway und Ihrer runway-Konfiguration—bevorzugen Sie die Ausrichtung neuer Modelle an App\Model, damit sie mit Autoloading und AGENTS.md übereinstimmen.
Beispiel eines Modells, das mit der Skeleton-Posts-Demo übereinstimmt:
<?php
declare(strict_types=1);
namespace App\Model;
use flight\ActiveRecord;
/**
* @property int $id
* @property string $title
* // …
*/
class Post extends ActiveRecord
{
protected array $relations = [];
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'posts');
}
}
Wenn ein älterer Generator immer noch app/records / app\records ausgibt, können Sie diese Konvention in Legacy-Apps beibehalten oder Dateien in app/Model/ verschieben und den Namespace an die Ordner-Groß-/Kleinschreibung anpassen.
Migrationen (Skeleton)
Das offizielle Skeleton enthält einen Projektbefehl (entdeckt aus app/commands/), wie zum Beispiel:
php runway migrate
Migrationen sind SQL-Dateien unter migrations/ (zum Beispiel YYYYMMDDHHMMSS_description.sql für SQLite und …_description.mysql.sql für MySQL), ausgewählt aus Ihrer Datenbanktreiber-Konfiguration / Umgebung. Exakte Flags und Verhalten werden von diesem Projektbefehl definiert—führen Sie php runway migrate --help in Ihrer App aus.
KI-Helfer
Runway stellt KI-orientierte Befehle bereit, die mit KI & Entwicklererfahrung verwendet werden:
php runway ai:init
php runway ai:generate-instructions
Diese speichern LLM-Anmeldedaten und generieren Projektanweisungen (hauptsächlich AGENTS.md). Beim Skeleton behandeln Sie AGENTS.md (und Scoped-Kopien unter app/) plus SECURITY.md als Quelle der Wahrheit für Agenten.
Alle Routen anzeigen
Dies zeigt alle Routen an, die derzeit bei Flight registriert sind.
php runway routes
Wenn Sie nur bestimmte Routen ansehen möchten, können Sie ein Flag übergeben, um die Routen zu filtern.
# Nur GET-Routen anzeigen
php runway routes --get
# Nur POST-Routen anzeigen
php runway routes --post
# usw.
Benutzerdefinierte Befehle zu Runway hinzufügen
Wenn Sie entweder ein Paket für Flight erstellen oder Ihre eigenen benutzerdefinierten Befehle in Ihr Projekt einfügen möchten, können Sie dies tun, indem Sie ein src/commands/, flight/commands/, app/commands/ oder commands/ Verzeichnis für Ihr Projekt/Paket erstellen. Wenn Sie weitere Anpassungen benötigen, siehe den Abschnitt unten zur Konfiguration.
Im Skeleton leben Projektbefehle in app/commands/ mit dem Namespace App\Command. Runway entdeckt sie über den Pfad; halten Sie diesen Ordner synchron mit dem Composer-Classmap/PSR-4, wie Ihr Projekt es bereits tut.
Um einen Befehl zu erstellen, erweitern Sie einfach die Klasse AbstractBaseCommand und implementieren Sie mindestens eine __construct-Methode und eine execute-Methode.
<?php
declare(strict_types=1);
namespace App\Command;
use flight\commands\AbstractBaseCommand;
class ExampleCommand extends AbstractBaseCommand
{
/**
* Konstruktor
*
* @param array<string,mixed> $config Konfiguration aus app/config/config.php
*/
public function __construct(array $config)
{
parent::__construct('make:example', 'Erstellt ein Beispiel für die Dokumentation', $config);
$this->argument('<funny-gif>', 'Der Name des lustigen Gifs');
}
/**
* Führt die Funktion aus
*
* @return void
*/
public function execute()
{
$io = $this->app()->io();
$io->info('Erstelle Beispiel...');
// Hier etwas tun
$io->ok('Beispiel erstellt!');
}
}
Siehe die adhocore/php-cli Dokumentation für weitere Informationen darüber, wie Sie Ihre eigenen benutzerdefinierten Befehle in Ihre Flight-Anwendung integrieren können!
Konfigurationsverwaltung
Da die Konfiguration ab v1.2.0 in app/config/config.php verschoben wurde, gibt es einige Hilfsbefehle zur Konfigurationsverwaltung.
Skeleton-Tipp: Halten Sie
config.phpals literalen PHP-Wert. Geheimnisse gehören in.env. Vermeiden Sie$_ENV[...]-Ausdrücke innerhalb vonconfig.php—config:setschreibt diese Datei als statische Daten um und könnte Geheimnisse in die Datei einbetten. Siehe Konfiguration.
Alte Konfiguration migrieren
Wenn Sie eine alte .runway-config.json-Datei haben, können Sie diese einfach mit dem folgenden Befehl zu app/config/config.php migrieren:
php runway config:migrate
Konfigurationswert setzen
Sie können einen Konfigurationswert mit dem Befehl config:set setzen. Dies ist nützlich, wenn Sie einen Konfigurationswert aktualisieren möchten, ohne die Datei zu öffnen.
php runway config:set app_root "app/"
Konfigurationswert abrufen
Sie können einen Konfigurationswert mit dem Befehl config:get abrufen.
php runway config:get app_root
Alle Runway-Konfigurationen
Wenn Sie die Konfiguration für Runway anpassen müssen, können Sie diese Werte in app/config/config.php setzen. Hier sind einige zusätzliche Konfigurationen, die Sie setzen können:
<?php
// app/config/config.php
return [
// ... andere Konfigurationswerte ...
'runway' => [
// Dies ist der Ort, an dem sich Ihr Anwendungsverzeichnis befindet
'app_root' => 'app/',
// Dies ist das Verzeichnis, in dem sich Ihre Root-Indexdatei befindet
'index_root' => 'public/',
// Dies sind die Pfade zu den Roots anderer Projekte
'root_paths' => [
'/home/user/different-project',
'/var/www/another-project'
],
// Basis-Pfade müssen wahrscheinlich nicht konfiguriert werden, aber es ist hier, wenn Sie es wollen
'base_paths' => [
'/includes/libs/vendor', // wenn Sie einen wirklich einzigartigen Pfad für Ihr Vendor-Verzeichnis oder so etwas haben
],
// Finale Pfade sind Orte innerhalb eines Projekts, um nach den Befehlsdateien zu suchen
'final_paths' => [
'src/diff-path/commands',
'app/module/admin/commands',
],
// Wenn Sie einfach den vollständigen Pfad hinzufügen möchten, nur zu (absolut oder relativ zum Projekt-Root)
'paths' => [
'/home/user/different-project/src/diff-path/commands',
'/var/www/another-project/app/module/admin/commands',
'app/my-unique-commands'
]
]
];
Konfiguration zugreifen
Wenn Sie die Konfigurationswerte effektiv zugreifen müssen, können Sie über die __construct-Methode oder die app()-Methode darauf zugreifen. Es ist auch wichtig zu beachten, dass wenn Sie eine app/config/services.php-Datei haben, diese Dienste auch für Ihren Befehl verfügbar sein werden.
public function execute()
{
$io = $this->app()->io();
// Konfiguration zugreifen
$app_root = $this->config['runway']['app_root'];
// Dienste zugreifen wie vielleicht eine Datenbankverbindung
$database = $this->config['database']
// ...
}
KI-Helfer-Wrapper
Runway hat einige Helfer-Wrapper, die es KI erleichtern, Befehle zu generieren. Sie können addOption und addArgument auf eine Weise verwenden, die sich ähnlich wie Symfony Console anfühlt. Dies ist hilfreich, wenn Sie KI-Tools zur Generierung Ihrer Befehle verwenden.
public function __construct(array $config)
{
parent::__construct('make:example', 'Erstellt ein Beispiel für die Dokumentation', $config);
// Das mode-Argument ist nullbar und standardmäßig vollständig optional
$this->addOption('name', 'Der Name des Beispiels', null);
}
Siehe auch
- Installation - Skeleton-Baum und create-project-Standardwerte
- Autoloading -
App\und Ordner-Groß-/Kleinschreibung - Dependency Injection - Dice + Engine-Injection für generierte Controller
- KI & Entwicklererfahrung -
ai:init,ai:generate-instructions,AGENTS.md - Active Record - Modelle verwendet mit
make:record/ SkeletonApp\Model - SimplePdo - DB-Verbindung verwendet von Skeleton-Migrationen und -Modellen
Awesome-plugins/tracy_extensions
Tracy Flight Panel-Erweiterungen
Dies ist ein Satz von Erweiterungen, um die Arbeit mit Flight etwas reichhaltiger zu gestalten.
- Flight - Alle Flight-Variablen analysieren.
- Datenbank - Alle Abfragen analysieren, die auf der Seite ausgeführt wurden (wenn Sie die Datenbankverbindung korrekt initiieren)
- Anfrage - Alle
$_SERVER-Variablen analysieren und alle globalen Nutzlasten ($_GET,$_POST,$_FILES) untersuchen - Session - Alle
$_SESSION-Variablen analysieren, falls Sessions aktiv sind. - Twig (optional) - Twig-Vorlagen-Renderzeit, Speicher und welche Vorlagen/Blöcke/Makros ausgeführt wurden analysieren (erfordert
twig/twigund einetwig_profile-Konfiguration)
Dies ist besonders praktisch mit dem offiziellen Skeleton, das standardmäßig Twig verwendet: das gleiche Layout KI-Tools folgen, wird auch deutlich auf der Tracy-Leiste angezeigt.
Dies ist das Panel

Und jedes Panel zeigt sehr hilfreiche Informationen über Ihre Anwendung an!

Klicken Sie hier, um den Code anzusehen.
Installation
Führen Sie composer require flightphp/tracy-extensions --dev aus und schon geht es los!
Twig ist keine harte Abhängigkeit des Pakets. Installieren Sie twig/twig nur, wenn Sie das Twig-Panel möchten (das Skeleton macht dies bereits für Views).
Konfiguration
Es gibt sehr wenig Konfiguration, die Sie vornehmen müssen, um dies zu starten. Sie müssen den Tracy-Debugger vor der Verwendung initiieren https://tracy.nette.org/en/guide:
<?php
use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;
// Bootstrap-Code
require __DIR__ . '/vendor/autoload.php';
Debugger::enable();
// Sie müssen möglicherweise Ihre Umgebung mit Debugger::enable(Debugger::DEVELOPMENT) angeben
// wenn Sie Datenbankverbindungen in Ihrer App verwenden, gibt es einen
// erforderlichen PDO-Wrapper, der NUR IN DER ENTWICKLUNG verwendet werden sollte (nicht in der Produktion bitte!)
// Er hat die gleichen Parameter wie eine reguläre PDO-Verbindung
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// oder wenn Sie dies an das Flight-Framework anhängen
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// jetzt wird jedes Mal, wenn Sie eine Abfrage ausführen, die Zeit, die Abfrage und die Parameter erfasst
// Dies verbindet die Punkte
if(Debugger::$showBar === true) {
// Dies muss false sein, oder Tracy kann nicht wirklich rendern :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app());
}
// mehr Code
Flight::start();
Zusätzliche Konfiguration
Session-Daten
Wenn Sie einen benutzerdefinierten Session-Handler haben (wie ghostff/session), können Sie jedes Array von Session-Daten an Tracy übergeben und es wird automatisch für Sie ausgegeben. Sie übergeben es mit dem session_data-Schlüssel im zweiten Parameter des TracyExtensionLoader-Konstruktors.
use Ghostff\Session\Session;
// oder flight\Session verwenden;
require 'vendor/autoload.php';
$app = Flight::app();
$app->register('session', Session::class);
if(Debugger::$showBar === true) {
// Dies muss false sein, oder Tracy kann nicht wirklich rendern :(
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}
// Routen und andere Dinge...
Flight::start();
Twig-Panel (optional)
Wenn Ihre App Twig verwendet (einschließlich des offiziellen Skeletons), können Sie Vorlagenmetriken auf der Tracy-Leiste anzeigen. Erstellen Sie ein Twig Profile, hängen Sie ProfilerExtension an Ihre Umgebung an, dann übergeben Sie dieses Profil an den Loader unter dem twig_profile-Schlüssel. Hängen Sie Profiling nur in der Entwicklung an.
<?php
use flight\debug\tracy\TracyExtensionLoader;
use flight\debug\tracy\TwigTracyExtension;
use Tracy\Debugger;
use Twig\Environment;
use Twig\Extension\ProfilerExtension;
use Twig\Loader\FilesystemLoader;
use Twig\Profiler\Profile;
$loader = new FilesystemLoader(__DIR__ . '/views');
$twig = new Environment($loader, [
'debug' => true,
'cache' => false,
]);
// Optional: Tracy-Dump-Helfer in Vorlagen verfügbar machen
// {{ dump(var) }}, {{ bdump(var) }}, {{ dumpe(var) }}
$twig->addExtension(new TwigTracyExtension());
$tracyConfig = [];
if (Debugger::$showBar === true) {
$profile = new Profile();
$twig->addExtension(new ProfilerExtension($profile));
$tracyConfig['twig_profile'] = $profile;
}
if (Debugger::$showBar === true) {
Flight::set('flight.content_length', false);
new TracyExtensionLoader(Flight::app(), $tracyConfig);
}
// Flight::render() zu Twig abbilden (Beispiel)
Flight::map('render', function (string $template, array $data = []) use ($twig) {
if (substr($template, -5) !== '.twig') {
$template .= '.twig';
}
echo $twig->render($template, $data);
});
Was das Panel anzeigt
- Gesamte Twig-Renderzeit und Speicher
- Vorlagen-/Block-/Makro-Aufrufzahlen
- Jede Vorlage, die gerendert wurde, mit ihrer eigenen Zeit und Speicher
Der Twig-Tab ist ausgeblendet, wenn für die Anfrage keine Vorlagen gerendert wurden, oder wenn Sie twig_profile weglassen (oder Twig nicht installiert haben) - andere Flight-Panels funktionieren weiterhin.
In einer skeleton-ähnlichen services.php, bauen Sie das gleiche $profile / ProfilerExtension auf, wenn Debug eingeschaltet ist, übergeben Sie twig_profile an TracyExtensionLoader, und verwenden Sie weiterhin Ihre gemeinsame Twig-Umgebung für $app->render().
Latte
PHP 8.1+ ist für diesen Abschnitt erforderlich.
Wenn Sie Latte in Ihrem Projekt installiert haben, hat Tracy eine native Integration mit Latte zur Analyse Ihrer Vorlagen. Sie registrieren einfach die Erweiterung mit Ihrer Latte-Instanz (dies ist Latte's eigene Tracy-Bridge, nicht das Twig-Panel oben).
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function($template, $data, $block = null) {
$latte = new Latte\Engine;
// andere Konfigurationen...
// die Erweiterung nur hinzufügen, wenn die Tracy Debug Bar aktiviert ist
if(Debugger::$showBar === true) {
// hier fügen Sie das Latte-Panel zu Tracy hinzu
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($template, $data, $block);
});
Siehe auch
- Tracy - Basis Tracy-Setup für Flight
- Twig - Von dem Skeleton und dem Twig-Panel verwendetes Templating
- Vorlagen - Wie Flight
renderzu Twig/Latte abbildet - Installation - Skeleton enthält tracy-extensions in dev
Awesome-plugins/apm
FlightPHP APM Dokumentation
Willkommen bei FlightPHP APM – dem persönlichen Performance-Coach für Ihre App! Dieser Leitfaden ist Ihr Wegweiser zum Einrichten, Verwenden und Beherrschen von Application Performance Monitoring (APM) mit FlightPHP. Egal, ob Sie langsame Anfragen aufspüren oder einfach nur über Latenzdiagramme schwärmen möchten – wir haben alles für Sie. Lassen Sie uns Ihre App schneller machen, Ihre Benutzer glücklicher und Ihre Debugging-Sitzungen zum Kinderspiel!
Sehen Sie sich eine Demo des Dashboards für die Flight Docs Seite an.

Warum APM wichtig ist
Stellen Sie sich vor, Ihre App ist ein belebtes Restaurant. Ohne eine Möglichkeit zu verfolgen, wie lange Bestellungen dauern oder wo es in der Küche stockt, können Sie nur raten, warum Kunden verärgert gehen. APM ist Ihr Sous-Chef – er überwacht jeden Schritt, von eingehenden Anfragen bis hin zu Datenbankabfragen, und markiert alles, was Sie ausbremst. Langsame Seiten verlieren Nutzer (Studien zeigen, dass 53 % abspringen, wenn eine Website länger als 3 Sekunden zum Laden braucht!), und APM hilft Ihnen, diese Probleme bevor sie schmerzen zu erkennen. Es ist proaktive Seelenruhe – weniger „Warum funktioniert das nicht?“-Momente, mehr „Schaut mal, wie geschmeidig das läuft!“-Erfolge.
Installation
Beginnen Sie mit Composer:
composer require flightphp/apm
Sie benötigen:
- PHP 7.4+: Hält uns kompatibel mit LTS-Linux-Distributionen und unterstützt modernes PHP.
- FlightPHP Core v3.15+: Das leichte Framework, das wir erweitern.
Unterstützte Datenbanken
FlightPHP APM unterstützt derzeit die folgenden Datenbanken zur Speicherung von Metriken:
- SQLite3: Einfach, dateibasiert und ideal für die lokale Entwicklung oder kleine Apps. Standardoption in den meisten Setups.
- MySQL/MariaDB: Ideal für größere Projekte oder Produktionsumgebungen, in denen robuste, skalierbare Speicherung benötigt wird.
Sie können Ihren Datenbanktyp während des Konfigurationsschritts auswählen (siehe unten). Stellen Sie sicher, dass Ihre PHP-Umgebung die erforderlichen Erweiterungen installiert hat (z. B. pdo_sqlite oder pdo_mysql).
Erste Schritte
Hier ist Ihre Schritt-für-Schritt-Anleitung zu APM-Höchstleistungen:
1. APM registrieren
Fügen Sie dies in Ihre index.php oder eine services.php-Datei ein, um mit der Nachverfolgung zu beginnen:
use flight\apm\logger\LoggerFactory;
use flight\database\SimplePdo;
use flight\Apm;
$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);
// Wenn Sie eine Datenbankverbindung hinzufügen
// Bevorzugen Sie SimplePdo (oder PdoQueryCapture von Tracy Extensions in der Entwicklung).
// Aktivieren Sie die APM-Abfrageverfolgung über das Options-Array (5. Argument).
$pdo = new SimplePdo('mysql:host=localhost;dbname=example', 'user', 'pass', null, [
'trackApmQueries' => true, // erforderlich, um Abfragen für das APM zu erfassen
]);
$Apm->addPdoConnection($pdo);
Was passiert hier?
LoggerFactory::create()holt Ihre Konfiguration (mehr dazu gleich) und richtet einen Logger ein – standardmäßig SQLite.Apmist der Star – er hört auf Flight-Events (Anfragen, Routen, Fehler usw.) und sammelt Metriken.bindEventsToFlightInstance($app)verknüpft alles mit Ihrer Flight-App.
Pro-Tipp: Sampling Wenn Ihre App viel zu tun hat, kann das Protokollieren jeder Anfrage überlasten. Verwenden Sie eine Sampling-Rate (0.0 bis 1.0):
$Apm = new Apm($ApmLogger, 0.1); // Protokolliert 10 % der Anfragen
Das hält die Leistung flott und liefert dennoch solide Daten.
2. Konfigurieren
Führen Sie dies aus, um Ihre .runway-config.json zu erstellen:
php vendor/bin/runway apm:init
Was macht das?
- Startet einen Assistenten, der fragt, woher die Rohmetriken kommen (Quelle) und wohin verarbeitete Daten gehen (Ziel).
- Standard ist SQLite – z. B.
sqlite:/tmp/apm_metrics.sqlitefür die Quelle, ein weiteres für das Ziel. - Sie erhalten am Ende eine Konfiguration wie:
{ "apm": { "source_type": "sqlite", "source_db_dsn": "sqlite:/tmp/apm_metrics.sqlite", "storage_type": "sqlite", "dest_db_dsn": "sqlite:/tmp/apm_metrics_processed.sqlite" } }
Dieser Vorgang fragt auch, ob Sie die Migrationen für dieses Setup ausführen möchten. Wenn Sie dies zum ersten Mal einrichten, lautet die Antwort ja.
Warum zwei Standorte? Rohmetriken häufen sich schnell an (denken Sie an ungefilterte Protokolle). Der Worker verarbeitet sie in ein strukturiertes Ziel für das Dashboard. Hält alles übersichtlich!
3. Metriken mit dem Worker verarbeiten
Der Worker wandelt Rohmetriken in Dashboard-bereite Daten um. Führen Sie ihn einmal aus:
php vendor/bin/runway apm:worker
Was macht er?
- Liest aus Ihrer Quelle (z. B.
apm_metrics.sqlite). - Verarbeitet bis zu 100 Metriken (Standard-Batch-Größe) in Ihr Ziel.
- Stoppt, wenn fertig oder wenn keine Metriken mehr vorhanden sind.
Dauerhaft laufen lassen Für Live-Apps möchten Sie eine kontinuierliche Verarbeitung. Hier sind Ihre Optionen:
-
Daemon-Modus:
php vendor/bin/runway apm:worker --daemonLäuft für immer und verarbeitet Metriken bei Ankunft. Ideal für Entwicklung oder kleine Setups.
-
Crontab: Fügen Sie dies zu Ihrer Crontab hinzu (
crontab -e):* * * * * php /path/to/project/vendor/bin/runway apm:workerWird jede Minute ausgeführt – perfekt für Produktion.
-
Tmux/Screen: Starten Sie eine abtrennbare Sitzung:
tmux new -s apm-worker php vendor/bin/runway apm:worker --daemon # Strg+B, dann D zum Abtrennen; `tmux attach -t apm-worker` zum WiederverbindenHält es auch nach dem Ausloggen am Laufen.
-
Benutzerdefinierte Anpassungen:
php vendor/bin/runway apm:worker --batch_size 50 --max_messages 1000 --timeout 300--batch_size 50: Verarbeitet 50 Metriken gleichzeitig.--max_messages 1000: Stoppt nach 1000 Metriken.--timeout 300: Beendet nach 5 Minuten.
Warum der Aufwand? Ohne den Worker ist Ihr Dashboard leer. Er ist die Brücke zwischen Rohprotokollen und umsetzbaren Erkenntnissen.
4. Dashboard starten
Sehen Sie sich die Vitalwerte Ihrer App an:
php vendor/bin/runway apm:dashboard
Was macht das?
- Startet einen PHP-Server unter
http://localhost:8001/apm/dashboard. - Zeigt Anfrageprotokolle, langsame Routen, Fehlerraten und mehr.
Anpassen:
php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php
--host 0.0.0.0: Von jeder IP erreichbar (praktisch für Remote-Ansichten).--port 8080: Verwenden Sie einen anderen Port, wenn 8001 belegt ist.--php-path: Geben Sie PHP an, wenn es nicht in Ihrem PATH ist.
Rufen Sie die URL in Ihrem Browser auf und erkunden Sie!
Produktionsmodus
Für die Produktion müssen Sie möglicherweise einige Techniken ausprobieren, um das Dashboard zum Laufen zu bringen, da wahrscheinlich Firewalls und andere Sicherheitsmaßnahmen vorhanden sind. Hier sind einige Optionen:
- Reverse-Proxy verwenden: Richten Sie Nginx oder Apache ein, um Anfragen an das Dashboard weiterzuleiten.
- SSH-Tunnel: Wenn Sie per SSH auf den Server zugreifen können, verwenden Sie
ssh -L 8080:localhost:8001 youruser@yourserver, um das Dashboard zu Ihrem lokalen Rechner zu tunneln. - VPN: Wenn Ihr Server hinter einem VPN steht, verbinden Sie sich damit und greifen Sie direkt auf das Dashboard zu.
- Firewall konfigurieren: Öffnen Sie Port 8001 für Ihre IP oder das Netzwerk des Servers. (oder welchen Port Sie auch immer eingestellt haben).
- Apache/Nginx konfigurieren: Wenn Sie einen Webserver vor Ihrer Anwendung haben, können Sie ihn für eine Domain oder Subdomain konfigurieren. Wenn Sie dies tun, setzen Sie das Dokumenten-Root auf
/path/to/your/project/vendor/flightphp/apm/dashboard
Anderes Dashboard gewünscht?
Sie können Ihr eigenes Dashboard erstellen, wenn Sie möchten! Schauen Sie im Verzeichnis vendor/flightphp/apm/src/apm/presenter nach Ideen, wie Sie die Daten für Ihr eigenes Dashboard präsentieren!
Dashboard-Funktionen
Das Dashboard ist Ihre APM-Zentrale – hier ist, was Sie sehen werden:
- Anfrageprotokoll: Jede Anfrage mit Zeitstempel, URL, Antwortcode und Gesamtzeit. Klicken Sie auf „Details“ für Middleware, Abfragen und Fehler.
- Langsamste Anfragen: Top 5 Anfragen, die Zeit verbrauchen (z. B. „/api/heavy“ mit 2,5s).
- Langsamste Routen: Top 5 Routen nach durchschnittlicher Zeit – ideal, um Muster zu erkennen.
- Fehlerrate: Prozentsatz fehlgeschlagener Anfragen (z. B. 2,3 % 500er).
- Latenz-Quantile: 95. (p95) und 99. (p99) Antwortzeiten – kennen Sie Ihre Worst-Case-Szenarien.
- Antwortcode-Diagramm: Visualisieren Sie 200er, 404er, 500er über die Zeit.
- Lange Abfragen/Middleware: Top 5 langsame Datenbankaufrufe und Middleware-Schichten.
- Cache-Treffer/Fehlschlag: Wie oft Ihr Cache den Tag rettet.
Extras:
- Nach „Letzte Stunde“, „Letzter Tag“ oder „Letzte Woche“ filtern.
- Dunkelmodus für nächtliche Sitzungen umschalten.
Beispiel:
Eine Anfrage an /users könnte zeigen:
- Gesamtzeit: 150ms
- Middleware:
AuthMiddleware->handle(50ms) - Abfrage:
SELECT * FROM users(80ms) - Cache: Treffer bei
user_list(5ms)
Benutzerdefinierte Events hinzufügen
Alles verfolgen – wie einen API-Aufruf oder Zahlungsvorgang:
use flight\apm\CustomEvent;
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('api_call', [
'endpoint' => 'https://api.example.com/users',
'response_time' => 0.25,
'status' => 200
]));
Wo wird es angezeigt? In den Anfragedetails des Dashboards unter „Benutzerdefinierte Events“ – erweiterbar mit hübscher JSON-Formatierung.
Anwendungsfall:
$start = microtime(true);
$apiResponse = file_get_contents('https://api.example.com/data');
$app->eventDispatcher()->trigger('apm.custom', new CustomEvent('external_api', [
'url' => 'https://api.example.com/data',
'time' => microtime(true) - $start,
'success' => $apiResponse !== false
]));
Jetzt sehen Sie, ob diese API Ihre App ausbremst!
Datenbanküberwachung
PDO-Abfragen so verfolgen:
use flight\database\SimplePdo;
$pdo = new SimplePdo('sqlite:/path/to/db.sqlite', null, null, null, [
'trackApmQueries' => true, // erforderlich, um Abfragen für das APM zu erfassen
]);
$Apm->addPdoConnection($pdo);
Was Sie erhalten:
- Abfragetext (z. B.
SELECT * FROM users WHERE id = ?) - Ausführungszeit (z. B. 0.015s)
- Zeilenanzahl (z. B. 42)
Achtung:
- Optional: Überspringen Sie dies, wenn Sie keine DB-Verfolgung benötigen.
- SimplePdo (bevorzugt): Verwenden Sie
SimplePdomittrackApmQueries => true. Der veraltetePdoWrapperfunktioniert noch (5. Konstruktor-Argumenttrue). Rohes Core-PDO ist noch nicht angebunden – bleiben Sie dran! - Leistungswarnung: Das Protokollieren jeder Abfrage auf einer datenbanklastigen Site kann Dinge verlangsamen. Verwenden Sie Sampling (
$Apm = new Apm($ApmLogger, 0.1)) zur Entlastung.
Beispielausgabe:
- Abfrage:
SELECT name FROM products WHERE price > 100 - Zeit: 0.023s
- Zeilen: 15
Worker-Optionen
Stellen Sie den Worker nach Ihren Wünschen ein:
--timeout 300: Stoppt nach 5 Minuten – gut zum Testen.--max_messages 500: Begrenzt auf 500 Metriken – hält es endlich.--batch_size 200: Verarbeitet 200 auf einmal – balanciert Geschwindigkeit und Speicher.--daemon: Läuft ununterbrochen – ideal für Live-Monitoring.
Beispiel:
php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600
Läuft eine Stunde lang, verarbeitet 100 Metriken gleichzeitig.
Request-ID in der App
Jede Anfrage hat eine eindeutige Request-ID zur Nachverfolgung. Sie können diese ID in Ihrer App verwenden, um Protokolle und Metriken zu korrelieren. Sie können beispielsweise die Request-ID zu einer Fehlerseite hinzufügen:
Flight::map('error', function($message) {
// Die Request-ID aus dem Antwort-Header X-Flight-Request-Id abrufen
$requestId = Flight::response()->getHeader('X-Flight-Request-Id');
// Zusätzlich könnten Sie sie aus der Flight-Variablen abrufen
// Diese Methode funktioniert nicht gut in Swoole oder anderen asynchronen Plattformen.
// $requestId = Flight::get('apm.request_id');
echo "Fehler: $message (Request-ID: $requestId)";
});
Upgrade
Wenn Sie auf eine neuere Version des APM upgraden, besteht die Möglichkeit, dass Datenbankmigrationen ausgeführt werden müssen. Sie können dies mit folgendem Befehl tun:
php vendor/bin/runway apm:migrate
Dies führt alle Migrationen aus, die benötigt werden, um das Datenbankschema auf die neueste Version zu aktualisieren.
Hinweis: Wenn Ihre APM-Datenbank groß ist, können diese Migrationen einige Zeit in Anspruch nehmen. Sie sollten diesen Befehl außerhalb der Stoßzeiten ausführen.
Upgrade von 0.4.3 -> 0.5.0
Wenn Sie von 0.4.3 auf 0.5.0 upgraden, müssen Sie folgenden Befehl ausführen:
php vendor/bin/runway apm:config-migrate
Dies migriert Ihre Konfiguration vom alten Format mit der .runway-config.json-Datei zum neuen Format, das die Schlüssel/Werte in der config.php-Datei speichert.
Alte Daten bereinigen
Um Ihre Datenbank sauber zu halten, können Sie alte Daten bereinigen. Dies ist besonders nützlich, wenn Sie eine stark frequentierte App betreiben und die Datenbankgröße überschaubar halten möchten. Sie können dies mit folgendem Befehl tun:
php vendor/bin/runway apm:purge
Dies entfernt alle Daten, die älter als 30 Tage sind, aus der Datenbank. Sie können die Anzahl der Tage anpassen, indem Sie einen anderen Wert an die --days-Option übergeben:
php vendor/bin/runway apm:purge --days 7
Dies entfernt alle Daten, die älter als 7 Tage sind, aus der Datenbank.
Fehlersuche
Stecken Sie fest? Versuchen Sie Folgendes:
-
Keine Dashboard-Daten?
- Läuft der Worker? Prüfen Sie
ps aux | grep apm:worker. - Stimmen die Konfigurationspfade? Überprüfen Sie, ob die DSNs in
.runway-config.jsonauf echte Dateien zeigen. - Führen Sie
php vendor/bin/runway apm:workermanuell aus, um ausstehende Metriken zu verarbeiten.
- Läuft der Worker? Prüfen Sie
-
Worker-Fehler?
- Werfen Sie einen Blick auf Ihre SQLite-Dateien (z. B.
sqlite3 /tmp/apm_metrics.sqlite "SELECT * FROM apm_metrics_log LIMIT 5"). - Prüfen Sie die PHP-Protokolle auf Stack-Traces.
- Werfen Sie einen Blick auf Ihre SQLite-Dateien (z. B.
-
Dashboard startet nicht?
- Port 8001 belegt? Verwenden Sie
--port 8080. - PHP nicht gefunden? Verwenden Sie
--php-path /usr/bin/php. - Firewall blockiert? Öffnen Sie den Port oder verwenden Sie
--host localhost.
- Port 8001 belegt? Verwenden Sie
-
Zu langsam?
- Senken Sie die Sampling-Rate:
$Apm = new Apm($ApmLogger, 0.05)(5 %). - Verringern Sie die Batch-Größe:
--batch_size 20.
- Senken Sie die Sampling-Rate:
-
Verfolgt keine Ausnahmen/Fehler?
- Wenn Sie Tracy für Ihr Projekt aktiviert haben, überschreibt dies die Fehlerbehandlung von Flight. Sie müssen Tracy deaktivieren und sicherstellen, dass
Flight::set('flight.handle_errors', true);gesetzt ist.
- Wenn Sie Tracy für Ihr Projekt aktiviert haben, überschreibt dies die Fehlerbehandlung von Flight. Sie müssen Tracy deaktivieren und sicherstellen, dass
-
Verfolgt keine Datenbankabfragen?
- Bevorzugen Sie
SimplePdomit['trackApmQueries' => true]als 5. Konstruktor-Argument (Options-Array). - Wenn Sie noch den veralteten
PdoWrapperverwenden, übergeben Sietrueals 5. Argument. - Rufen Sie
$Apm->addPdoConnection($pdo)nach dem Erstellen der Verbindung auf.
- Bevorzugen Sie
Awesome-plugins/tracy
Tracy
Tracy ist ein erstaunlicher Fehlerhandler, der mit Flight verwendet werden kann. Er verfügt über eine Reihe von Panels, die Ihnen beim Debuggen Ihrer Anwendung helfen können. Er ist auch sehr einfach zu erweitern und eigene Panels hinzuzufügen. Das Flight Team hat einige Panels speziell für Flight-Projekte mit dem Plugin flightphp/tracy-extensions erstellt (Flight-Variablen, DB-Abfragen, Request, Session und ein optionales Twig-Panel, wenn Sie ein Profiler-Profil übergeben – siehe Tracy Extensions).
Installation
Mit Composer installieren. Und Sie möchten dies tatsächlich ohne die Dev-Version installieren, da Tracy mit einer Produktions-Fehlerbehandlungskomponente kommt.
composer require tracy/tracy
Grundkonfiguration
Es gibt einige grundlegende Konfigurationsoptionen, um zu beginnen. Sie können mehr darüber in der Tracy-Dokumentation lesen.
require 'vendor/autoload.php';
use Tracy\Debugger;
// Tracy aktivieren
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // manchmal müssen Sie explizit sein (auch Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // Sie können auch ein Array von IP-Adressen angeben
// Hier werden Fehler und Ausnahmen protokolliert. Stellen Sie sicher, dass dieses Verzeichnis existiert und beschreibbar ist.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // alle Fehler anzeigen
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // alle Fehler außer veralteten Hinweisen
if (Debugger::$showBar) {
$app->set('flight.content_length', false); // wenn die Debugger-Leiste sichtbar ist, kann die Content-Length nicht von Flight gesetzt werden
// Dies ist spezifisch für die Tracy-Erweiterung für Flight, wenn Sie diese eingebunden haben
// andernfalls kommentieren Sie dies aus.
new TracyExtensionLoader($app);
}
Hilfreiche Tipps
Wenn Sie Ihren Code debuggen, gibt es einige sehr hilfreiche Funktionen, um Daten für Sie auszugeben.
bdump($var)- Dies wird die Variable in die Tracy-Leiste in einem separaten Panel ausgeben.dumpe($var)- Dies wird die Variable ausgeben und dann sofort beenden.
Awesome-plugins/active_record
Flight Active Record
Ein Active Record ist eine Zuordnung einer Datenbank-Entität zu einem PHP-Objekt. Einfach gesagt: Wenn Sie eine Tabelle users in Ihrer Datenbank haben, können Sie eine Zeile in dieser Tabelle in eine User-Klasse und ein $user-Objekt in Ihrem Codebase "übersetzen". Siehe einfaches Beispiel.
Klicken Sie hier für das Repository auf GitHub.
Einfaches Beispiel
Nehmen wir an, Sie haben die folgende Tabelle:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
password TEXT
);
Nun können Sie eine neue Klasse einrichten, um diese Tabelle darzustellen:
/**
* Eine ActiveRecord-Klasse ist normalerweise Singular
*
* Es wird dringend empfohlen, die Eigenschaften der Tabelle hier als Kommentare hinzuzufügen
*
* @property int $id
* @property string $name
* @property string $password
*/
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
// Sie können es auf diese Weise einstellen
parent::__construct($database_connection, 'users');
// oder auf diese Weise
parent::__construct($database_connection, null, [ 'table' => 'users']);
}
}
Nun beobachten Sie, wie die Magie geschieht!
// Für SQLite
$database_connection = new PDO('sqlite:test.db'); // Dies ist nur ein Beispiel, Sie würden wahrscheinlich eine echte Datenbankverbindung verwenden
// Für MySQL
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');
// oder MySQLi
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// oder MySQLi mit nicht-objektbasierter Erstellung
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');
$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// oder $user->save();
echo $user->id; // 1
$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// Hier können Sie $user->save() nicht verwenden, da es sonst als Update interpretiert wird!
echo $user->id; // 2
Und so einfach war es, einen neuen Benutzer hinzuzufügen! Nun, da eine Benutzerzeile in der Datenbank vorhanden ist, wie holen Sie sie heraus?
$user->find(1); // Findet id = 1 in der Datenbank und gibt es zurück.
echo $user->name; // 'Bobby Tables'
Und was, wenn Sie alle Benutzer finden möchten?
$users = $user->findAll();
Was ist mit einer bestimmten Bedingung?
$users = $user->like('name', '%mamma%')->findAll();
Sehen Sie, wie viel Spaß das macht? Lassen Sie uns es installieren und loslegen!
Installation
Einfach mit Composer installieren
composer require flightphp/active-record
Verwendung
Dies kann als eigenständige Bibliothek oder mit dem Flight PHP Framework verwendet werden. Ganz nach Ihrem Wunsch.
Eigenständig
Stellen Sie sicher, dass Sie eine PDO-Verbindung an den Konstruktor übergeben.
$pdo_connection = new PDO('sqlite:test.db'); // Dies ist nur ein Beispiel, Sie würden wahrscheinlich eine echte Datenbankverbindung verwenden
$User = new User($pdo_connection);
Möchten Sie nicht immer die Datenbankverbindung im Konstruktor festlegen? Sehen Sie Datenbankverbindungsverwaltung für andere Ideen!
Als Methode in Flight registrieren
Wenn Sie das Flight PHP Framework verwenden, können Sie die ActiveRecord-Klasse als Service registrieren, müssen es aber ehrlich gesagt nicht tun.
Flight::register('user', 'User', [ $pdo_connection ]);
// Dann können Sie es in einem Controller, einer Funktion usw. so verwenden.
Flight::user()->find(1);
runway Methoden
runway ist ein CLI-Tool für Flight, das einen benutzerdefinierten Befehl für diese Bibliothek hat.
# Verwendung
php runway make:record database_table_name [class_name]
# Beispiel
php runway make:record users
Dies erstellt eine neue Klasse im Verzeichnis app/records/ als UserRecord.php mit folgendem Inhalt:
<?php
declare(strict_types=1);
namespace app\records;
/**
* ActiveRecord-Klasse für die users-Tabelle.
* @link https://docs.flightphp.com/awesome-plugins/active-record
*
* @property int $id
* @property string $username
* @property string $email
* @property string $password_hash
* @property string $created_dt
*/
class UserRecord extends \flight\ActiveRecord
{
/**
* @var array $relations Setzt die Beziehungen für das Modell
* https://docs.flightphp.com/awesome-plugins/active-record#relationships
*/
protected array $relations = [
// 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
];
/**
* Konstruktor
* @param mixed $databaseConnection Die Verbindung zur Datenbank
*/
public function __construct($databaseConnection)
{
parent::__construct($databaseConnection, 'users');
}
}
CRUD-Funktionen
find($id = null) : boolean|ActiveRecord
Findet einen Datensatz und weist ihn dem aktuellen Objekt zu. Wenn Sie eine $id übergeben, führt es eine Suche im Primärschlüssel mit diesem Wert durch. Wenn nichts übergeben wird, findet es einfach den ersten Datensatz in der Tabelle.
Zusätzlich können Sie andere Hilfsmethoden übergeben, um Ihre Tabelle abzufragen.
// Findet einen Datensatz mit einigen Bedingungen im Voraus
$user->notNull('password')->orderBy('id DESC')->find();
// Findet einen Datensatz anhand einer spezifischen ID
$id = 123;
$user->find($id);
findAll(): array<int,ActiveRecord>
Findet alle Datensätze in der von Ihnen angegebenen Tabelle.
$user->findAll();
isHydrated(): boolean (v0.4.0)
Gibt true zurück, wenn der aktuelle Datensatz hydriert wurde (aus der Datenbank abgerufen).
$user->find(1);
// Wenn ein Datensatz mit Daten gefunden wird...
$user->isHydrated(); // true
insert(): boolean|ActiveRecord
Fügt den aktuellen Datensatz in die Datenbank ein.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Textbasierte Primärschlüssel
Wenn Sie einen textbasierten Primärschlüssel haben (wie eine UUID), können Sie den Primärschlüsselwert vor dem Einfügen auf eine von zwei Arten festlegen.
$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // oder $user->save();
Oder Sie lassen den Primärschlüssel automatisch durch Ereignisse generieren.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
// Sie können den primaryKey auch auf diese Weise statt im Array oben festlegen.
$this->primaryKey = 'uuid';
}
protected function beforeInsert(self $self) {
$self->uuid = uniqid(); // oder wie auch immer Sie Ihre eindeutigen IDs generieren müssen
}
}
Wenn Sie den Primärschlüssel vor dem Einfügen nicht festlegen, wird er auf rowid gesetzt und die Datenbank generiert ihn für Sie, aber er wird nicht persistiert, da dieses Feld möglicherweise nicht in Ihrer Tabelle existiert. Deshalb wird empfohlen, das Ereignis zu verwenden, um dies automatisch für Sie zu handhaben.
update(): boolean|ActiveRecord
Aktualisiert den aktuellen Datensatz in der Datenbank.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();
save(): boolean|ActiveRecord
Fügt den aktuellen Datensatz in die Datenbank ein oder aktualisiert ihn. Wenn der Datensatz eine ID hat, wird er aktualisiert, andernfalls eingefügt.
$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();
Hinweis: Wenn Sie Beziehungen in der Klasse definiert haben, speichert er rekursiv auch diese Beziehungen, wenn sie definiert, instanziiert und schmutzige Daten zum Aktualisieren haben. (v0.4.0 und höher)
delete(): boolean
Löscht den aktuellen Datensatz aus der Datenbank.
$user->gt('id', 0)->orderBy('id desc')->find();
$user->delete();
Sie können auch mehrere Datensätze löschen, indem Sie vorher eine Suche ausführen.
$user->like('name', 'Bob%')->delete();
dirty(array $dirty = []): ActiveRecord
Schmutzige Daten beziehen sich auf die Daten, die in einem Datensatz geändert wurden.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
// Bis zu diesem Punkt ist nichts "schmutzig".
$user->email = 'test@example.com'; // Nun gilt email als "schmutzig", da es geändert wurde.
$user->update();
// Nun gibt es keine schmutzigen Daten mehr, da sie aktualisiert und in der Datenbank persistiert wurden
$user->password = password_hash()'newpassword'); // Nun ist das schmutzig
$user->dirty(); // Ohne Parameter löscht es alle schmutzigen Einträge.
$user->update(); // Nichts wird aktualisiert, da nichts als schmutzig erfasst wurde.
$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // Sowohl name als auch password werden aktualisiert.
copyFrom(array $data): ActiveRecord (v0.4.0)
Dies ist ein Alias für die dirty()-Methode. Es ist etwas klarer, was Sie tun.
$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // Sowohl name als auch password werden aktualisiert.
isDirty(): boolean (v0.4.0)
Gibt true zurück, wenn der aktuelle Datensatz geändert wurde.
$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true
reset(bool $include_query_data = true): ActiveRecord
Setzt den aktuellen Datensatz auf seinen anfänglichen Zustand zurück. Das ist wirklich gut für Schleifenverhalten zu verwenden. Wenn Sie true übergeben, setzt es auch die Abfragedaten zurück, die verwendet wurden, um das aktuelle Objekt zu finden (Standardverhalten).
$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);
foreach($users as $user) {
$user_company->reset(); // Mit einer sauberen Tafel beginnen
$user_company->user_id = $user->id;
$user_company->company_id = $some_company_id;
$user_company->insert();
}
getBuiltSql(): string (v0.4.1)
Nachdem Sie eine find(), findAll(), insert(), update() oder save()-Methode ausgeführt haben, können Sie den generierten SQL-Code abrufen und für Debugging-Zwecke verwenden.
SQL-Abfragemethoden
select(string $field1 [, string $field2 ... ])
Sie können nur einige Spalten in einer Tabelle auswählen, wenn Sie möchten (es ist performanter bei wirklich breiten Tabellen mit vielen Spalten)
$user->select('id', 'name')->find();
from(string $table)
Sie können technisch eine andere Tabelle wählen! Warum nicht?!
$user->select('id', 'name')->from('user')->find();
join(string $table_name, string $join_condition)
Sie können sogar zu einer anderen Tabelle in der Datenbank joinen.
$user->join('contacts', 'contacts.user_id = users.id')->find();
where(string $where_conditions)
Sie können einige benutzerdefinierte where-Argumente setzen (Sie können in dieser where-Anweisung keine Parameter setzen)
$user->where('id=1 AND name="demo"')->find();
Sicherheitshinweis - Sie könnten versucht sein, etwas wie $user->where("id = '{$id}' AND name = '{$name}'")->find(); zu tun. Bitte TUN SIE DAS NICHT!!! Das ist anfällig für SQL-Injection-Angriffe. Es gibt viele Artikel online, suchen Sie bitte nach "sql injection attacks php" und Sie finden viele Artikel zu diesem Thema. Der richtige Weg, das mit dieser Bibliothek zu handhaben, ist, anstelle dieser where()-Methode etwas wie $user->eq('id', $id)->eq('name', $name)->find(); zu tun. Wenn Sie es absolut tun müssen, hat die PDO-Bibliothek $pdo->quote($var), um es für Sie zu escapen. Nur nach der Verwendung von quote() können Sie es in einer where()-Anweisung verwenden.
group(string $group_by_statement)/groupBy(string $group_by_statement)
Gruppieren Sie Ihre Ergebnisse nach einer bestimmten Bedingung.
$user->select('COUNT(*) as count')->groupBy('name')->findAll();
order(string $order_by_statement)/orderBy(string $order_by_statement)
Sortieren Sie die zurückgegebene Abfrage auf eine bestimmte Weise.
$user->orderBy('name DESC')->find();
limit(string $limit)/limit(int $offset, int $limit)
Begrenzen Sie die Anzahl der zurückgegebenen Datensätze. Wenn eine zweite Ganzzahl gegeben ist, wird sie als offset, limit genau wie in SQL verwendet.
$user->orderby('name DESC')->limit(0, 10)->findAll();
WHERE-Bedingungen
equal(string $field, mixed $value) / eq(string $field, mixed $value)
Where field = $value
$user->eq('id', 1)->find();
notEqual(string $field, mixed $value) / ne(string $field, mixed $value)
Where field <> $value
$user->ne('id', 1)->find();
isNull(string $field)
Where field IS NULL
$user->isNull('id')->find();
isNotNull(string $field) / notNull(string $field)
Where field IS NOT NULL
$user->isNotNull('id')->find();
greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)
Where field > $value
$user->gt('id', 1)->find();
lessThan(string $field, mixed $value) / lt(string $field, mixed $value)
Where field < $value
$user->lt('id', 1)->find();
greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)
Where field >= $value
$user->ge('id', 1)->find();
lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)
Where field <= $value
$user->le('id', 1)->find();
like(string $field, mixed $value) / notLike(string $field, mixed $value)
Where field LIKE $value oder field NOT LIKE $value
$user->like('name', 'de')->find();
in(string $field, array $values) / notIn(string $field, array $values)
Where field IN($value) oder field NOT IN($value)
$user->in('id', [1, 2])->find();
between(string $field, array $values)
Where field BETWEEN $value AND $value1
$user->between('id', [1, 2])->find();
OR-Bedingungen
Es ist möglich, Ihre Bedingungen in einer OR-Anweisung zu umschließen. Dies geschieht entweder mit den Methoden startWrap() und endWrap() oder indem Sie den 3. Parameter der Bedingung nach Feld und Wert ausfüllen.
// Methode 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Dies wird zu `id = 1 AND (name = 'demo' OR name = 'test')` ausgewertet
// Methode 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Dies wird zu `id = 1 OR name = 'demo'` ausgewertet
Beziehungen
Mit dieser Bibliothek können Sie mehrere Arten von Beziehungen festlegen. Sie können one-to-many- und one-to-one-Beziehungen zwischen Tabellen festlegen. Dies erfordert eine etwas zusätzliche Einrichtung in der Klasse im Voraus.
Das Festlegen des $relations-Arrays ist nicht schwer, aber das Erraten der korrekten Syntax kann verwirrend sein.
protected array $relations = [
// Sie können den Schlüssel beliebig benennen. Der Name des ActiveRecord ist wahrscheinlich gut. Beispiel: user, contact, client
'user' => [
// erforderlich
// self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
self::HAS_ONE, // dies ist der Typ der Beziehung
// erforderlich
'Some_Class', // dies ist die "andere" ActiveRecord-Klasse, auf die verwiesen wird
// erforderlich
// abhängig vom Beziehungstyp
// self::HAS_ONE = der Fremdschlüssel, der auf den Join verweist
// self::HAS_MANY = der Fremdschlüssel, der auf den Join verweist
// self::BELONGS_TO = der lokale Schlüssel, der auf den Join verweist
'local_or_foreign_key',
// Nur so nebenbei, dies joinet auch nur zum Primärschlüssel des "anderen" Modells
// optional
[ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // zusätzliche Bedingungen, die Sie beim Joinen der Beziehung wollen
// $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))
// optional
'back_reference_name' // dies ist, wenn Sie diese Beziehung zurück auf sich selbst referenzieren möchten, z. B. $user->contact->user;
];
]
class User extends ActiveRecord{
protected array $relations = [
'contacts' => [ self::HAS_MANY, Contact::class, 'user_id' ],
'contact' => [ self::HAS_ONE, Contact::class, 'user_id' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
}
class Contact extends ActiveRecord{
protected array $relations = [
'user' => [ self::BELONGS_TO, User::class, 'user_id' ],
'user_with_backref' => [ self::BELONGS_TO, User::class, 'user_id', [], 'contact' ],
];
public function __construct($database_connection)
{
parent::__construct($database_connection, 'contacts');
}
}
Nun haben wir die Referenzen eingerichtet, sodass wir sie sehr einfach verwenden können!
$user = new User($pdo_connection);
// Finden Sie den neuesten Benutzer.
$user->notNull('id')->orderBy('id desc')->find();
// Kontakte mit der Beziehung abrufen:
foreach($user->contacts as $contact) {
echo $contact->id;
}
// Oder wir können es umgekehrt tun.
$contact = new Contact();
// Einen Kontakt finden
$contact->find();
// Benutzer mit der Beziehung abrufen:
echo $contact->user->name; // Dies ist der Benutzername
Ziemlich cool, oder?
Eager Loading
Überblick
Eager Loading löst das N+1-Abfrageproblem, indem Beziehungen im Voraus geladen werden. Anstatt für jede Beziehung eines Datensatzes eine separate Abfrage auszuführen, holt Eager Loading alle verwandten Daten in nur einer zusätzlichen Abfrage pro Beziehung.
Hinweis: Eager Loading ist nur ab v0.7.0 verfügbar.
Grundlegende Verwendung
Verwenden Sie die with()-Methode, um anzugeben, welche Beziehungen eager geladen werden sollen:
// Benutzer mit ihren Kontakten in 2 Abfragen laden statt N+1
$users = $user->with('contacts')->findAll();
foreach ($users as $u) {
foreach ($u->contacts as $contact) {
echo $contact->email; // Keine zusätzliche Abfrage!
}
}
Mehrere Beziehungen
Mehrere Beziehungen auf einmal laden:
$users = $user->with(['contacts', 'profile', 'settings'])->findAll();
Beziehungstypen
HAS_MANY
// Alle Kontakte für jeden Benutzer eager laden
$users = $user->with('contacts')->findAll();
foreach ($users as $u) {
// $u->contacts ist bereits als Array geladen
foreach ($u->contacts as $contact) {
echo $contact->email;
}
}
HAS_ONE
// Einen Kontakt für jeden Benutzer eager laden
$users = $user->with('contact')->findAll();
foreach ($users as $u) {
// $u->contact ist bereits als Objekt geladen
echo $u->contact->email;
}
BELONGS_TO
// Elternbenutzer für alle Kontakte eager laden
$contacts = $contact->with('user')->findAll();
foreach ($contacts as $c) {
// $c->user ist bereits geladen
echo $c->user->name;
}
Mit find()
Eager Loading funktioniert sowohl mit findAll() als auch find() :
$user = $user->with('contacts')->find(1);
// Benutzer und alle ihre Kontakte in 2 Abfragen geladen
Leistungsverbesserungen
Ohne Eager Loading (N+1-Problem):
$users = $user->findAll(); // 1 Abfrage
foreach ($users as $u) {
$contacts = $u->contacts; // N Abfragen (eine pro Benutzer!)
}
// Gesamt: 1 + N Abfragen
Mit Eager Loading:
$users = $user->with('contacts')->findAll(); // 2 Abfragen gesamt
foreach ($users as $u) {
$contacts = $u->contacts; // 0 zusätzliche Abfragen!
}
// Gesamt: 2 Abfragen (1 für Benutzer + 1 für alle Kontakte)
Für 10 Benutzer reduziert das die Abfragen von 11 auf 2 - eine Reduktion um 82%!
Wichtige Hinweise
- Eager Loading ist vollständig optional - Lazy Loading funktioniert weiterhin wie zuvor
- Bereits geladene Beziehungen werden automatisch übersprungen
- Back-Referenzen funktionieren mit Eager Loading
- Beziehungs-Callbacks werden während des Eager Loadings berücksichtigt
Einschränkungen
- Verschachteltes Eager Loading (z. B. with(['contacts.addresses']) ) wird derzeit nicht unterstützt
- Eager-Load-Einschränkungen über Closures werden in dieser Version nicht unterstützt
Benutzerdefinierte Daten festlegen
Manchmal müssen Sie etwas Einzigartiges an Ihr ActiveRecord anhängen, wie eine benutzerdefinierte Berechnung, die einfacher sein könnte, einfach an das Objekt angehängt zu werden, das dann z. B. an eine Vorlage übergeben wird.
setCustomData(string $field, mixed $value)
Sie hängen die benutzerdefinierten Daten mit der setCustomData()-Methode an.
$user->setCustomData('page_view_count', $page_view_count);
Und dann referenzieren Sie es einfach wie eine normale Objekteigenschaft.
echo $user->page_view_count;
Ereignisse
Eine weitere super coole Funktion dieser Bibliothek sind Ereignisse. Ereignisse werden zu bestimmten Zeiten ausgelöst, basierend auf bestimmten Methoden, die Sie aufrufen. Sie sind sehr hilfreich, um Daten automatisch für Sie einzurichten.
onConstruct(ActiveRecord $ActiveRecord, array &config)
Das ist wirklich hilfreich, wenn Sie eine Standardverbindung oder Ähnliches festlegen müssen.
// index.php oder bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
//
//
//
// User.php
class User extends flight\ActiveRecord {
protected function onConstruct(self $self, array &$config) { // Vergessen Sie nicht die & Referenz
// Sie könnten das tun, um die Verbindung automatisch zu setzen
$config['connection'] = Flight::db();
// oder das
$self->transformAndPersistConnection(Flight::db());
// Sie können auch den Tabellennamen auf diese Weise festlegen.
$config['table'] = 'users';
}
}
beforeFind(ActiveRecord $ActiveRecord)
Das ist wahrscheinlich nur nützlich, wenn Sie jede Abfrage manipulieren müssen.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFind(self $self) {
// Immer id >= 0 ausführen, wenn das Ihr Ding ist
$self->gte('id', 0);
}
}
afterFind(ActiveRecord $ActiveRecord)
Diese ist wahrscheinlich nützlicher, wenn Sie immer etwas Logik ausführen müssen, jedes Mal, wenn dieser Datensatz abgerufen wird. Müssen Sie etwas entschlüsseln? Müssen Sie eine benutzerdefinierte Zählabfrage ausführen (nicht performant, aber egal)?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFind(self $self) {
// Etwas entschlüsseln
$self->secret = yourDecryptFunction($self->secret, $some_key);
// Vielleicht etwas Benutzerdefiniertes speichern wie eine Abfrage???
$self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count'];
}
}
beforeFindAll(ActiveRecord $ActiveRecord)
Das ist wahrscheinlich nur nützlich, wenn Sie jede Abfrage manipulieren müssen.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeFindAll(self $self) {
// Immer id >= 0 ausführen, wenn das Ihr Ding ist
$self->gte('id', 0);
}
}
afterFindAll(array<int,ActiveRecord> $results)
Ähnlich wie afterFind(), aber Sie können es für alle Datensätze tun!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterFindAll(array $results) {
foreach($results as $self) {
// Etwas Cooles tun wie afterFind()
}
}
}
beforeInsert(ActiveRecord $ActiveRecord)
Wirklich hilfreich, wenn Sie einige Standardwerte jedes Mal festlegen müssen.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// Einige vernünftige Standardwerte setzen
if(!$self->created_date) {
$self->created_date = gmdate('Y-m-d');
}
if(!$self->password) {
$self->password = password_hash((string) microtime(true));
}
}
}
afterInsert(ActiveRecord $ActiveRecord)
Vielleicht haben Sie einen Anwendungsfall, um Daten nach dem Einfügen zu ändern?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// Machen Sie, was Sie wollen
Flight::cache()->set('most_recent_insert_id', $self->id);
// oder was auch immer....
}
}
beforeUpdate(ActiveRecord $ActiveRecord)
Wirklich hilfreich, wenn Sie einige Standardwerte jedes Mal bei einer Aktualisierung festlegen müssen.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeInsert(self $self) {
// Einige vernünftige Standardwerte setzen
if(!$self->updated_date) {
$self->updated_date = gmdate('Y-m-d');
}
}
}
afterUpdate(ActiveRecord $ActiveRecord)
Vielleicht haben Sie einen Anwendungsfall, um Daten nach der Aktualisierung zu ändern?
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function afterInsert(self $self) {
// Machen Sie, was Sie wollen
Flight::cache()->set('most_recently_updated_user_id', $self->id);
// oder was auch immer....
}
}
beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)
Das ist nützlich, wenn Sie Ereignisse haben möchten, die sowohl bei Einfügungen als auch bei Aktualisierungen auftreten. Ich spare mir die lange Erklärung, aber ich bin sicher, Sie können erraten, was es ist.
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeSave(self $self) {
$self->last_updated = gmdate('Y-m-d H:i:s');
}
}
beforeDelete(ActiveRecord $ActiveRecord)/afterDelete(ActiveRecord $ActiveRecord)
Nicht sicher, was Sie hier tun möchten, aber keine Urteile hier! Legen Sie los!
class User extends flight\ActiveRecord {
public function __construct($database_connection)
{
parent::__construct($database_connection, 'users');
}
protected function beforeDelete(self $self) {
echo 'He was a brave soldier... :cry-face:';
}
}
Datenbankverbindungsverwaltung
Wenn Sie diese Bibliothek verwenden, können Sie die Datenbankverbindung auf einige verschiedene Weisen festlegen. Sie können die Verbindung im Konstruktor festlegen, über eine Konfigurationsvariable $config['connection'] oder über setDatabaseConnection() (v0.4.1).
$pdo_connection = new PDO('sqlite:test.db'); // als Beispiel
$user = new User($pdo_connection);
// oder
$user = new User(null, [ 'connection' => $pdo_connection ]);
// oder
$user = new User();
$user->setDatabaseConnection($pdo_connection);
Wenn Sie vermeiden möchten, immer eine $database_connection jedes Mal festzulegen, wenn Sie ein Active Record aufrufen, gibt es Wege darum!
// index.php oder bootstrap.php
// Dies als registrierte Klasse in Flight festlegen
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);
// User.php
class User extends flight\ActiveRecord {
public function __construct(array $config = [])
{
$database_connection = $config['connection'] ?? Flight::db();
parent::__construct($database_connection, 'users', $config);
}
}
// Und nun keine Argumente erforderlich!
$user = new User();
Hinweis: Wenn Sie Unit-Tests planen, kann das auf diese Weise einige Herausforderungen für Unit-Tests hinzufügen, aber insgesamt, da Sie Ihre Verbindung mit
setDatabaseConnection()oder$config['connection']injizieren können, ist es nicht zu schlecht.
Wenn Sie die Datenbankverbindung aktualisieren müssen, z. B. wenn Sie ein langes CLI-Skript ausführen und die Verbindung alle paar Mal aktualisieren müssen, können Sie die Verbindung mit $your_record->setDatabaseConnection($pdo_connection) neu setzen.
Mitwirkung
Bitte tun Sie das. :D
Einrichtung
Wenn Sie beitragen, stellen Sie sicher, dass Sie composer test-coverage ausführen, um 100% Testabdeckung zu wahren (das ist keine echte Unit-Test-Abdeckung, eher wie Integrationstests).
Stellen Sie auch sicher, dass Sie composer beautify und composer phpcs ausführen, um Linting-Fehler zu beheben.
Lizenz
MIT
Awesome-plugins/latte
Latte
Latte ist ein vollwertiges Templating-Engine, das sehr einfach zu bedienen ist und näher an der PHP-Syntax liegt als Twig oder Smarty. Es ist auch sehr einfach zu erweitern und eigene Filter und Funktionen hinzuzufügen.
Installation
Installieren Sie es mit Composer.
composer require latte/latte
Grundlegende Konfiguration
Es gibt einige grundlegende Konfigurationsoptionen, um zu starten. Sie können mehr darüber in der Latte-Dokumentation lesen.
require 'vendor/autoload.php';
$app = Flight::app();
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Wo Latte speziell seinen Cache speichert
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
$latte->render($finalPath, $data, $block);
});
Einfaches Layout-Beispiel
Hier ist ein einfaches Beispiel für eine Layout-Datei. Dies ist die Datei, die verwendet wird, um alle Ihre anderen Views zu umschließen.
<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
<head>
<title>{$title ? $title . ' - '}My App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
<!-- Ihre Navigations-Elemente hier -->
</nav>
</header>
<div id="content">
<!-- Hier liegt die Magie -->
{block content}{/block}
</div>
<div id="footer">
© Copyright
</div>
</body>
</html>
Und jetzt haben wir Ihre Datei, die in diesem Content-Block gerendert wird:
<!-- app/views/home.latte -->
<!-- Dies teilt Latte mit, dass diese Datei "innerhalb" der layout.latte-Datei liegt -->
{extends layout.latte}
<!-- Dies ist der Inhalt, der innerhalb des Layouts im Content-Block gerendert wird -->
{block content}
<h1>Startseite</h1>
<p>Willkommen in meiner App!</p>
{/block}
Wenn Sie dies in Ihrer Funktion oder Ihrem Controller rendern, würden Sie etwas Ähnliches tun:
// Einfache Route
Flight::route('/', function () {
Flight::render('home.latte', [
'title' => 'Startseite'
]);
});
// Oder wenn Sie einen Controller verwenden
Flight::route('/', [HomeController::class, 'index']);
// HomeController.php
class HomeController
{
public function index()
{
Flight::render('home.latte', [
'title' => 'Startseite'
]);
}
}
Sehen Sie sich die Latte-Dokumentation für weitere Informationen an, wie Sie Latte in vollem Umfang nutzen können!
Debugging mit Tracy
PHP 8.1+ ist für diesen Abschnitt erforderlich.
Sie können auch Tracy verwenden, um Ihre Latte-Template-Dateien direkt aus der Box heraus zu debuggen! Wenn Sie Tracy bereits installiert haben, müssen Sie die Latte-Erweiterung zu Tracy hinzufügen.
// services.php
use Tracy\Debugger;
$app->map('render', function(string $template, array $data, ?string $block): void {
$latte = new Latte\Engine;
// Wo Latte speziell seinen Cache speichert
$latte->setTempDirectory(__DIR__ . '/../cache/');
$finalPath = Flight::get('flight.views.path') . $template;
// Dies fügt die Erweiterung nur hinzu, wenn die Tracy-Debug-Bar aktiviert ist
if (Debugger::$showBar === true) {
// Hier fügen Sie das Latte-Panel zu Tracy hinzu
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
}
$latte->render($finalPath, $data, $block);
});Awesome-plugins/awesome_plugins
Fantastische Plugins
Flight ist unglaublich erweiterbar. Es gibt eine Reihe von Plugins, die verwendet werden können, um Funktionalität zu Ihrer Flight-Anwendung hinzuzufügen. Einige werden offiziell vom Flight-Team unterstützt und andere sind Micro-/Lite-Bibliotheken, die Ihnen den Einstieg erleichtern.
KI-Tools
Flight kann mit KI-gestützten Plugins noch cooler gemacht werden.
- Flight MCP - Ein Plugin zur Integration von MCP (Model Control Protocol) mit Flight, das nahtlose KI-gestützte Funktionalität ermöglicht. Hauptsächlich auf die Dokumentationsseiten ausgerichtet, hilft es, Token-Kosten niedrig zu halten, indem es die aktuellsten Informationen über Ihre Flight-Projekte bereitstellt.
API-Dokumentation
API-Dokumentation ist für jede API entscheidend. Sie hilft Entwicklern zu verstehen, wie sie mit Ihrer API interagieren und was sie als Rückgabe erwarten können. Es gibt ein paar Tools, die Ihnen helfen, API-Dokumentation für Ihre Flight-Projekte zu generieren.
- FlightPHP OpenAPI Generator - Blog-Beitrag von Daniel Schreiber darüber, wie man die OpenAPI-Spezifikation mit FlightPHP verwendet, um Ihre API mit einem API-First-Ansatz aufzubauen.
- SwaggerUI - Swagger UI ist ein großartiges Tool, das Ihnen hilft, API-Dokumentation für Ihre Flight-Projekte zu generieren. Es ist sehr einfach zu bedienen und kann an Ihre Bedürfnisse angepasst werden. Dies ist die PHP-Bibliothek, die Ihnen hilft, die Swagger-Dokumentation zu generieren.
Anwendungsleistungsüberwachung (APM)
Anwendungsleistungsüberwachung (APM) ist für jede Anwendung entscheidend. Sie hilft Ihnen zu verstehen, wie Ihre Anwendung performt und wo die Engpässe sind. Es gibt eine Reihe von APM-Tools, die mit Flight verwendet werden können.
- official flightphp/apm - Flight APM ist eine einfache APM-Bibliothek, die zur Überwachung Ihrer Flight-Anwendungen verwendet werden kann. Sie kann zur Leistungsüberwachung Ihrer Anwendung verwendet werden und hilft Ihnen, Engpässe zu identifizieren.
Async
Flight ist bereits ein schnelles Framework, aber wenn man ihm einen Turbomotor verpasst, macht alles noch mehr Spaß (und ist herausfordernder)!
- flightphp/async - Offizielle Flight Async-Bibliothek. Diese Bibliothek ist eine einfache Möglichkeit, asynchrone Verarbeitung zu Ihrer Anwendung hinzuzufügen. Sie verwendet Swoole/Openswoole unter der Haube, um eine einfache und effektive Möglichkeit zur asynchronen Ausführung von Aufgaben bereitzustellen.
Autorisierung/Berechtigungen
Autorisierung und Berechtigungen sind für jede Anwendung entscheidend, die Kontrollen erfordert, wer auf was zugreifen kann.
- official flightphp/permissions - Offizielle Flight Permissions-Bibliothek. Diese Bibliothek ist eine einfache Möglichkeit, Benutzer- und Anwendungsebene-Berechtigungen zu Ihrer Anwendung hinzuzufügen.
Authentifizierung
Authentifizierung ist für Anwendungen unerlässlich, die Benutzeridentität überprüfen und API-Endpunkte sichern müssen.
- firebase/php-jwt - JSON Web Token (JWT)-Bibliothek für PHP. Eine einfache und sichere Möglichkeit, token-basierte Authentifizierung in Ihren Flight-Anwendungen zu implementieren. Perfekt für zustandslose API-Authentifizierung, Routen-Schutz mit Middleware und die Implementierung von OAuth-ähnlichen Autorisierungsflüssen.
Caching
Caching ist eine großartige Möglichkeit, Ihre Anwendung zu beschleunigen. Es gibt eine Reihe von Caching-Bibliotheken, die mit Flight verwendet werden können.
- official flightphp/cache - Leichte, einfache und eigenständige PHP-Datei-Caching-Klasse
CLI
CLI-Anwendungen sind eine großartige Möglichkeit, mit Ihrer Anwendung zu interagieren. Sie können sie verwenden, um Controller zu generieren, alle Routen anzuzeigen und mehr.
- official flightphp/runway - Runway ist eine CLI-Anwendung, die Ihnen hilft, Ihre Flight-Anwendungen zu verwalten.
Cookies
Cookies sind eine großartige Möglichkeit, kleine Datenmengen auf der Client-Seite zu speichern. Sie können verwendet werden, um Benutzereinstellungen, Anwendungseinstellungen und mehr zu speichern.
- overclokk/cookie - PHP Cookie ist eine PHP-Bibliothek, die eine einfache und effektive Möglichkeit zur Cookie-Verwaltung bietet.
Debugging
Debugging ist entscheidend, wenn Sie in Ihrer lokalen Umgebung entwickeln. Es gibt ein paar Plugins, die Ihr Debugging-Erlebnis verbessern können.
- tracy/tracy - Dies ist ein vollständiger Fehlerhandler, der mit Flight verwendet werden kann. Er hat eine Reihe von Panels, die Ihnen helfen, Ihre Anwendung zu debuggen. Er ist auch sehr einfach zu erweitern und eigene Panels hinzuzufügen.
- official flightphp/tracy-extensions - Wird mit dem Tracy Fehlerhandler verwendet, fügt dieses Plugin ein paar zusätzliche Panels hinzu, um speziell bei Flight-Projekten zu debuggen.
Datenbanken
Datenbanken sind das Herzstück der meisten Anwendungen. Hier speichern und rufen Sie Daten ab. Einige Datenbankbibliotheken sind einfach Wrapper zum Schreiben von Abfragen und einige sind vollwertige ORMs.
- official flightphp/core SimplePdo - Offizieller Flight PDO-Helper, der Teil des Kerns ist. Dies ist ein moderner Wrapper mit praktischen Hilfsmethoden wie
insert(),update(),delete()undtransaction(), um Datenbankoperationen zu vereinfachen. Alle Ergebnisse werden als Collections zurückgegeben für flexiblen Array-/Objektzugriff. Kein ORM, nur eine bessere Möglichkeit, mit PDO zu arbeiten. - deprecated flightphp/core PdoWrapper - Offizieller Flight PDO-Wrapper, der Teil des Kerns ist (veraltet ab v3.18.0). Verwenden Sie stattdessen SimplePdo.
- official flightphp/active-record - Offizielles Flight ActiveRecord ORM/Mapper. Eine großartige kleine Bibliothek zum einfachen Abrufen und Speichern von Daten in Ihrer Datenbank.
- byjg/php-migration - Plugin zur Nachverfolgung aller Datenbankänderungen für Ihr Projekt.
- knifelemon/easy-query - Leichtgewichtiger, fließender SQL-Query-Builder, der SQL und Parameter für vorbereitete Anweisungen generiert. Funktioniert hervorragend mit SimplePdo.
Verschlüsselung
Verschlüsselung ist für jede Anwendung entscheidend, die sensible Daten speichert. Das Verschlüsseln und Entschlüsseln der Daten ist nicht besonders schwer, aber die ordnungsgemäße Speicherung des Verschlüsselungsschlüssels kann schwierig sein. Das Wichtigste ist, Ihren Verschlüsselungsschlüssel niemals in einem öffentlichen Verzeichnis zu speichern oder ihn in Ihr Code-Repository zu übertragen.
- defuse/php-encryption - Dies ist eine Bibliothek, die zum Verschlüsseln und Entschlüsseln von Daten verwendet werden kann. Der Einstieg ist ziemlich einfach, um mit dem Verschlüsseln und Entschlüsseln von Daten zu beginnen.
Das Versenden von E-Mails ist ein Kernbedarf der meisten Webanwendungen - Willkommensnachrichten, Passwort-Resets, Benachrichtigungen. Diese Bibliotheken machen es schmerzlos und halten die Zustellbarkeit solide.
- ryanstubbs/flightmail - FlightMail umhüllt Symfony Mailer mit einer fließenden, Flight-freundlichen API. Senden Sie über SMTP oder jeden großen Anbieter über einfache DSN-Strings, leiten Sie verschiedene Anbieter pro Nachricht und rendern Sie Inhalte mit Twig- oder Latte-Templates. Dies ist ein inoffizielles Plugin für Flight und wird nicht vom Flight-Team gepflegt.
Job-Queue
Job-Queues sind wirklich hilfreich, um Aufgaben asynchron zu verarbeiten. Dies kann das Senden von E-Mails, die Verarbeitung von Bildern oder alles sein, was nicht in Echtzeit erledigt werden muss.
- n0nag0n/simple-job-queue - Simple Job Queue ist eine Bibliothek, die zur asynchronen Verarbeitung von Jobs verwendet werden kann. Sie kann mit beanstalkd, MySQL/MariaDB, SQLite und PostgreSQL verwendet werden.
Session
Sessions sind für APIs nicht wirklich nützlich, aber für den Aufbau einer Webanwendung können Sessions entscheidend für die Aufrechterhaltung von Zustand und Login-Informationen sein.
- official flightphp/session - Offizielle Flight Session-Bibliothek. Dies ist eine einfache Session-Bibliothek, die zum Speichern und Abrufen von Session-Daten verwendet werden kann. Sie verwendet die integrierte Session-Verwaltung von PHP.
- Ghostff/Session - PHP Session-Manager (non-blocking, flash, segment, session encryption). Verwendet PHP open_ssl für optionale Verschlüsselung/Entschlüsselung von Session-Daten.
Templating
Templating ist das Herzstück jeder Webanwendung mit einer Benutzeroberfläche. Es gibt eine Reihe von Templating-Engines, die mit Flight verwendet werden können.
- deprecated flightphp/core View - Dies ist eine sehr grundlegende Templating-Engine, die Teil des Kerns ist. Es wird nicht empfohlen, sie zu verwenden, wenn Sie mehr als ein paar Seiten in Ihrem Projekt haben.
- latte/latte - Latte ist eine vollwertige Templating-Engine, die sehr einfach zu bedienen ist und sich näher an der PHP-Syntax als Twig oder Smarty anfühlt. Sie ist auch sehr einfach zu erweitern und eigene Filter und Funktionen hinzuzufügen.
- twig/twig - Twig ist eine flexible, schnelle und sichere Template-Engine (dieselbe, die von Symfony verwendet wird). KI-Tools und viele PHP-Entwickler kennen sie gut, sie maskiert Ausgabe standardmäßig automatisch und hat ein riesiges Ökosystem von Erweiterungen.
- knifelemon/comment-template - CommentTemplate ist eine leistungsstarke PHP-Template-Engine mit Asset-Kompilierung, Template-Vererbung und Variablenverarbeitung. Bietet automatische CSS/JS-Minifizierung, Caching, Base64-Kodierung und optionale Flight PHP Framework-Integration.
WordPress-Integration
Möchten Sie Flight in Ihrem WordPress-Projekt verwenden? Es gibt ein praktisches Plugin dafür!
- n0nag0n/wordpress-integration-for-flight-framework - Dieses WordPress-Plugin ermöglicht es Ihnen, Flight direkt neben WordPress auszuführen. Es ist perfekt, um benutzerdefinierte APIs, Microservices oder sogar vollständige Anwendungen zu Ihrer WordPress-Website mit dem Flight-Framework hinzuzufügen. Super nützlich, wenn Sie das Beste aus beiden Welten wollen!
Mitwirken
Haben Sie ein Plugin, das Sie teilen möchten? Senden Sie einen Pull-Request, um es zur Liste hinzuzufügen!
Media
Medien
Wir haben versucht, so viel wie möglich von den verschiedenen Medientypen im Internet rund um Flight zu finden. Siehe unten für verschiedene Ressourcen, die Sie nutzen können, um mehr über Flight zu erfahren.
Artikel und Berichte
- Unit Testing and SOLID Principles von Brian Fenton (2015?)
- PHP Web Framework Flight von ojambo (2025)
- Define, Generate, and Implement: An API-First Approach with OpenAPI Generator and FlightPHP von Daniel Schreiber (2025)
- Best PHP Micro Frameworks for 2024 von n0nag0n (2024)
- Creating a RESTful API with Flight Framework von n0nag0n (2024)
- Building a Simple Blog with Flight Part 2 von n0nag0n (2024)
- Building a Simple Blog with Flight Part 1 von n0nag0n (2024)
- 🚀 Build a Simple CRUD API in PHP with the Flight Framework von soheil-khaledabadi (2024)
- Building a PHP Web Application with the Flight Micro-framework von Arthur C. Codex (2023)
- Best PHP Frameworks for Web Development in 2024 von Ravikiran A S (2023)
- Top 12 PHP Frameworks: A Comprehensive Guide for 2023 von marketing kbk (2023)
- 5 PHP Frameworks You've (Probably) Never Heard of von n0nag0n (2022)
- 12 top PHP frameworks for web developers to consider in 2023 von Anna Monus (2022)
- The Best PHP Microframeworks on a Cloud Server von Shahzeb Ahmed (2021)
- PHP framework: Top 15 powerful ones for your web development von AHT Tech (2020)
- Easy PHP Routing with FlightPHP von Lucas Conceição (2019)
- Trying Out New PHP Framework (Flight) von Leon (2017)
- Setting up FlightPHP to work with Backbonejs von Timothy Tocci (2015)
Videos und Tutorials
- Build a Flight PHP App with MVC & MariaDB in 10 Minutes! (Beginner Friendly) von ojamboshop (2025)
- Create a REST API for IoT Devices Using PHP & FlightPHP - ESP32 API von IoT Craft Hub (2024)
- PHP Flight Framework Simple Introductory Video von n0nag0n (2024)
- Set header HTTP code in Flightphp (3 Solutions!!) von Roel Van de Paar (2024)
- PHP Flight Framework Tutorial. Super easy API Project! von n0nag0n (2022)
- Aplicación web CRUD con php y mysql y bootstrap usando flight von Devlopteca - Oscar Uh (2021)
- DevOps & SysAdmins: Lighttpd rewrite rule for Flight PHP microframework von Roel Van de Paar (2021)
- Tutorial REST API Flight PHP #PART2 INSERT TABLE Info #Code (Tagalog) von Info Singkat Official (2020)
- Tutorial REST API Flight PHP #PART1 Info #Code (Tagalog) von Info Singkat Official (2020)
- How To Create JSON REST API IN PHP - Part 2 von Codewife (2018)
- How To Create JSON REST API IN PHP - Part 1 von Codewife (2018)
- Teste Micro Frameworks PHP - Flight PHP, Lumen, Slim 3 e Laravel von Codemarket (2016)
- Tutorial 1 Flight PHP - Instalación von absagg (2014)
- Tutorial 2 Flight PHP - Route parte 1 von absagg (2014)
Fehlt etwas?
Fehlt etwas, das Sie geschrieben oder aufgenommen haben? Lassen Sie es uns mit einem Issue oder Pull Request wissen!
Examples
Brauchen Sie einen schnellen Einstieg?
Sie haben zwei Optionen, um mit einem neuen Flight-Projekt zu starten:
- Full Skeleton Boilerplate: Ein umfassenderes Beispiel mit Controllern und Views.
- Single File Skeleton Boilerplate: Eine einzelne Datei, die alles enthält, was Sie benötigen, um Ihre App in einer einzigen einfachen Datei auszuführen.
Community-beigetragene Beispiele:
- flightravel: FlightPHP mit Laravel-Verzeichnissen, mit PHP-Tools + GH Actions
- fleact - Ein FlightPHP-Starter-Kit mit ReactJS-Integration.
- flastro - Ein FlightPHP-Starter-Kit mit Astro-Integration.
- velt - Velt ist eine schnelle und einfache Svelte-Starter-Vorlage mit einem FlightPHP-Backend.
- vite-flightphp - FlightPHP mit modernem Frontend (Vite + Tailwind CSS) mit Hot Reload.
Brauchen Sie etwas Inspiration?
Obwohl diese nicht offiziell vom Flight-Team gesponsert werden, könnten sie Ihnen Ideen geben, wie Sie Ihre eigenen Projekte mit Flight strukturieren!
- ASC REST API Spell Checker - Eine leichte REST-API für die arabische Rechtschreibprüfung, aufgebaut mit FlightPHP und ArPHP-Bibliothek. Diese API bietet Funktionen zur arabischen Text-Rechtschreibprüfung, einschließlich Erkennung von Fehlern und Korrekturvorschlägen.
- Eventify - Eventify ist eine Single-Page-App, die Veranstaltungsorganisatoren mit Teilnehmern verbindet. Aufgebaut mit PHP (FlightPHP), JavaScript und MySQL, verfügt sie über JWT-Authentifizierung, Veranstaltungsmanagement und RESTful-API-Dokumentation mit OpenAPI.
- Ivox Car Rental - Ivox Car Rental ist eine Single-Page, mobilfreundliche Autovermietungs-Web-Anwendung, aufgebaut mit PHP (FlightPHP), JavaScript und MySQL. Sie unterstützt Benutzerregistrierung, Durchsuchen und Buchen von Autos, während Admins Autos, Benutzer und Buchungen verwalten können. Die App verfügt über eine REST-API, JWT-Authentifizierung und ein responsives Design für ein modernes Miet-Erlebnis.
- Decay - Flight v3 mit HTMX und SleekDB, alles über Zombies! (Demo)
- Flight Example Blog - Flight v3 mit Middleware, Controllern, Active Record und Latte.
- Flight CRUD RESTful API - Ein einfaches CRUD-API-Projekt mit dem Flight-Framework, das eine grundlegende Struktur für neue Benutzer bietet, um schnell eine PHP-Anwendung mit CRUD-Operationen und Datenbankverbindung einzurichten. Das Projekt demonstriert, wie man Flight für die RESTful-API-Entwicklung verwendet, und ist somit ein ideales Lernwerkzeug für Anfänger und ein nützliches Starter-Kit für erfahrene Entwickler.
- Flight School Management System - Flight v3
- Paste Bin with Comments - Flight v3
- Basic Skeleton App
- Example Wiki
- The IT-Innovator PHP Framework Application
- LittleEducationalCMS (Spanish)
- Italian Yellow Pages API
- Generic Content Management System (with....very little documentation)
- A tiny php framework based on Flight and medoo.
- Example MVC Application
- Production ready Flight Boilerplate - Ein produktionsreifes Authentifizierungs-Framework, das Ihnen Wochen der Entwicklung erspart. Features enterprise-grade Sicherheit: 2FA/TOTP, LDAP-Integration, Azure SSO, intelligente Ratenbegrenzung, Sitzungsfingerabdruck, Schutz vor Brute-Force-Angriffen, Sicherheits-Analytics-Dashboard, umfassende Audit-Protokollierung und granulare rollenbasierte Zugriffssteuerung.
Möchten Sie Ihr eigenes Beispiel teilen?
Wenn Sie ein Projekt haben, das Sie teilen möchten, reichen Sie bitte einen Pull Request ein, um es zu dieser Liste hinzuzufügen!
Install/install
Installationsanleitung
Es gibt einige grundlegende Voraussetzungen, bevor Sie Flight installieren können. Nämlich benötigen Sie:
- Installieren Sie PHP auf Ihrem System
- Installieren Sie Composer für die beste Entwicklererfahrung.
Grundinstallation
Wenn Sie Composer verwenden, können Sie den folgenden Befehl ausführen:
composer require flightphp/core
Dadurch werden nur die Flight-Kerndateien auf Ihrem System abgelegt. Sie müssen die Projektstruktur, Layout, Abhängigkeiten, Konfigurationen, Autoloading usw. selbst definieren. Diese Methode stellt sicher, dass außer Flight keine weiteren Abhängigkeiten installiert werden.
Sie können die Dateien auch herunterladen und direkt in Ihr Webverzeichnis entpacken.
Die Grundinstallation ist perfekt zum Lernen, für Micro-APIs und für Copy-Paste-Experimente. Für ein vollständiges App-Layout, das Menschen und KI-Codierungswerkzeuge auf dieselbe Weise nachvollziehen können, verwenden Sie das unten empfohlene Grundgerüst.
Empfohlene Installation
Es wird dringend empfohlen, für neue Projekte mit der flightphp/skeleton-App zu starten. Die Installation ist ein Kinderspiel.
composer create-project flightphp/skeleton my-project/
cd my-project/
composer start
# optionale Beispiel-DB + Posts-Demo
php runway migrate
Dieser Schritt richtet die Projektstruktur, das Composer-PSR-4-Autoloading, die Konfiguration sowie Werkzeuge wie Tracy, Tracy Extensions und Runway ein. Außerdem wird eine AGENTS.md im Root-Verzeichnis (sowie bereichsbezogene Kopien unter app/) mitgeliefert, damit KI-Assistenten ein gemeinsames Layout mit Ihnen teilen – siehe KI & Entwicklererfahrung.
Was das Grundgerüst Ihnen bietet
project-root/
├── AGENTS.md # KI / Agenten-Quelle der Wahrheit
├── SECURITY.md # Sicherheitserwartungen
├── .env.example # Geheimnisse / Deploy-Overlays (kopiert nach .env)
├── public/index.php # Nur Web-Einstiegspunkt
├── app/
│ ├── config/ # Bootstrap, Routen, Services, config_sample.php
│ ├── Controller/ # App\Controller\* (PascalCase-Ordner!)
│ ├── Middleware/ # App\Middleware\*
│ ├── Model/ # App\Model\* (ActiveRecord)
│ ├── Utils/ # Config, Env, DatabaseFactory
│ ├── commands/ # Runway-CLI-Befehle
│ ├── views/ # Twig-Templates (*.twig)
│ ├── cache/
│ └── log/
├── migrations/ # SQL-Migrationen (.sql / .mysql.sql)
└── tests/ # PHPUnit
Namespaces folgen der Ordner-Schreibweise. Composer mappt "App\\": "app/", also:
| Pfad auf der Festplatte | Namespace |
|---|---|
app/Controller/HomeController.php |
App\Controller\HomeController |
app/Middleware/… |
App\Middleware\… |
app/Model/… |
App\Model\… |
app/Utils/… |
App\Utils\… |
Auf Linux ist app/controller/ nicht dasselbe wie app/Controller/. Das Autoloading unterscheidet zwischen Groß- und Kleinschreibung – verwenden Sie die PascalCase-Ordner des Grundgerüsts. Details: Autoloading.
Standard-Stack (neue Projekte): Twig-Views, SimplePdo + ActiveRecord, Dice mit Engine-Injektion (bevorzugen Sie kein Flight:: innerhalb von App-Klassen), optional SQLite nach php runway migrate.
create-project kopiert typischerweise app/config/config_sample.php → config.php und .env.example → .env, falls vorhanden. Routen leben in app/config/routes.php; Services und DI leben in app/config/services.php.
Dokumentation ↔ Grundgerüst: Diese Dokumentation lehrt die Flight-APIs (oft mit kurzen
Flight::-Beispielen). Das Grundgerüst legt die Anwendungsstruktur fest. Wenn Sie Code unterapp/hinzufügen, folgen Sie dem Grundgerüstbaum; nutzen Sie die Dokumentation für Methodennamen, Optionen und Plugins.
Konfigurieren Sie Ihren Webserver
Integrierter PHP-Entwicklungsserver
Dies ist bei weitem der einfachste Weg, um loszulegen. Sie können den integrierten Server verwenden, um Ihre Anwendung auszuführen, und sogar SQLite als Datenbank nutzen (solange sqlite3 auf Ihrem System installiert ist), ohne viel zu benötigen! Führen Sie einfach den folgenden Befehl aus, sobald PHP installiert ist:
php -S localhost:8000
# oder mit der Grundgerüst-App
composer start
Öffnen Sie dann Ihren Browser und gehen Sie zu http://localhost:8000.
Wenn Sie das Dokumentenverzeichnis Ihres Projekts in ein anderes Verzeichnis legen möchten (z. B. Ihr Projekt ist ~/myproject, aber Ihr Dokumentenverzeichnis ist ~/myproject/public/), können Sie den folgenden Befehl ausführen, sobald Sie sich im Verzeichnis ~/myproject befinden:
php -S localhost:8000 -t public/
# bei der Grundgerüst-App ist dies bereits konfiguriert
composer start
Öffnen Sie dann Ihren Browser und gehen Sie zu http://localhost:8000.
Apache
Stellen Sie sicher, dass Apache bereits auf Ihrem System installiert ist. Wenn nicht, googeln Sie, wie Sie Apache auf Ihrem System installieren.
Bearbeiten Sie für Apache Ihre .htaccess-Datei mit folgendem Inhalt:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Hinweis: Wenn Sie Flight in einem Unterverzeichnis verwenden müssen, fügen Sie die Zeile
RewriteBase /subdir/direkt nachRewriteEngine Onhinzu.
Hinweis: Wenn Sie alle Serverdateien schützen möchten, z. B. eine DB- oder Umgebungsdatei. Fügen Sie dies in Ihre
.htaccess-Datei ein:
RewriteEngine On
RewriteRule ^(.*)$ index.php
Nginx
Stellen Sie sicher, dass Nginx bereits auf Ihrem System installiert ist. Wenn nicht, googeln Sie, wie Sie Nginx auf Ihrem System installieren.
Für Nginx fügen Sie Ihrer Server-Deklaration Folgendes hinzu:
server {
location / {
try_files $uri $uri/ /index.php;
}
}
Erstellen Sie Ihre index.php-Datei
Wenn Sie eine Grundinstallation durchführen, benötigen Sie etwas Code, um loszulegen.
<?php
// Wenn Sie Composer verwenden, binden Sie den Autoloader ein.
require 'vendor/autoload.php';
// Wenn Sie Composer nicht verwenden, laden Sie das Framework direkt
// require 'flight/Flight.php';
// Definieren Sie dann eine Route und weisen Sie eine Funktion zur Bearbeitung der Anfrage zu.
Flight::route('/', function () {
echo 'hello world!';
});
// Starten Sie schließlich das Framework.
Flight::start();
Bei der Grundgerüst-App bootet der öffentliche Einstiegspunkt nur die Anwendung. Routen werden in app/config/routes.php registriert (typischerweise [App\Controller\…::class, 'method'], damit Dice Abhängigkeiten injizieren kann). Services, Twig, SimplePdo und der Container werden in app/config/services.php verdrahtet. Diese Struktur ist beabsichtigt, damit KI-Tools und Menschen jedes Mal dieselben Stellen bearbeiten.
PHP installieren
Wenn auf Ihrem System bereits php installiert ist, überspringen Sie diese Anweisungen und fahren Sie mit dem Download-Abschnitt fort.
macOS
PHP mit Homebrew installieren
-
Homebrew installieren (falls nicht bereits installiert):
- Öffnen Sie das Terminal und führen Sie aus:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- Öffnen Sie das Terminal und führen Sie aus:
-
PHP installieren:
- Installieren Sie die neueste Version:
brew install php - Um eine bestimmte Version zu installieren, z. B. PHP 8.1:
brew tap shivammathur/php brew install shivammathur/php/php@8.1
- Installieren Sie die neueste Version:
-
Zwischen PHP-Versionen wechseln:
- Entfernen Sie die Verknüpfung der aktuellen Version und verknüpfen Sie die gewünschte Version:
brew unlink php brew link --overwrite --force php@8.1 - Überprüfen Sie die installierte Version:
php -v
- Entfernen Sie die Verknüpfung der aktuellen Version und verknüpfen Sie die gewünschte Version:
Windows 10/11
PHP manuell installieren
-
PHP herunterladen:
- Besuchen Sie PHP für Windows und laden Sie die neueste oder eine bestimmte Version (z. B. 7.4, 8.0) als Nicht-Thread-sichere ZIP-Datei herunter.
-
PHP entpacken:
- Entpacken Sie die heruntergeladene ZIP-Datei nach
C:\php.
- Entpacken Sie die heruntergeladene ZIP-Datei nach
-
PHP zum System-PATH hinzufügen:
- Gehen Sie zu Systemeigenschaften > Umgebungsvariablen.
- Suchen Sie unter Systemvariablen den Eintrag Path und klicken Sie auf Bearbeiten.
- Fügen Sie den Pfad
C:\php(oder den Ort, an den Sie PHP entpackt haben) hinzu. - Klicken Sie auf OK, um alle Fenster zu schließen.
-
PHP konfigurieren:
- Kopieren Sie
php.ini-developmentnachphp.ini. - Bearbeiten Sie
php.ini, um PHP nach Bedarf zu konfigurieren (z. B.extension_dirfestlegen, Erweiterungen aktivieren).
- Kopieren Sie
-
PHP-Installation überprüfen:
- Öffnen Sie die Eingabeaufforderung und führen Sie aus:
php -v
- Öffnen Sie die Eingabeaufforderung und führen Sie aus:
Mehrere PHP-Versionen installieren
-
Wiederholen Sie die obigen Schritte für jede Version und platzieren Sie jede in einem separaten Verzeichnis (z. B.
C:\php7,C:\php8). -
Wechseln Sie zwischen den Versionen, indem Sie die System-PATH-Variable so anpassen, dass sie auf das gewünschte Versionsverzeichnis zeigt.
Ubuntu (20.04, 22.04 usw.)
PHP mit apt installieren
-
Paketlisten aktualisieren:
- Öffnen Sie das Terminal und führen Sie aus:
sudo apt update
- Öffnen Sie das Terminal und führen Sie aus:
-
PHP installieren:
- Installieren Sie die neueste PHP-Version:
sudo apt install php - Um eine bestimmte Version zu installieren, z. B. PHP 8.1:
sudo apt install php8.1
- Installieren Sie die neueste PHP-Version:
-
Zusätzliche Module installieren (optional):
- Um beispielsweise MySQL-Unterstützung zu installieren:
sudo apt install php8.1-mysql
- Um beispielsweise MySQL-Unterstützung zu installieren:
-
Zwischen PHP-Versionen wechseln:
- Verwenden Sie
update-alternatives:sudo update-alternatives --set php /usr/bin/php8.1
- Verwenden Sie
-
Installierte Version überprüfen:
- Führen Sie aus:
php -v
- Führen Sie aus:
Rocky Linux
PHP mit yum/dnf installieren
-
EPEL-Repository aktivieren:
- Öffnen Sie das Terminal und führen Sie aus:
sudo dnf install epel-release
- Öffnen Sie das Terminal und führen Sie aus:
-
Remi-Repository installieren:
- Führen Sie aus:
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm sudo dnf module reset php
- Führen Sie aus:
-
PHP installieren:
- Um die Standardversion zu installieren:
sudo dnf install php - Um eine bestimmte Version zu installieren, z. B. PHP 7.4:
sudo dnf module install php:remi-7.4
- Um die Standardversion zu installieren:
-
Zwischen PHP-Versionen wechseln:
- Verwenden Sie den
dnf-Modulbefehl:sudo dnf module reset php sudo dnf module enable php:remi-8.0 sudo dnf install php
- Verwenden Sie den
-
Installierte Version überprüfen:
- Führen Sie aus:
php -v
- Führen Sie aus:
Allgemeine Hinweise
- Für Entwicklungsumgebungen ist es wichtig, die PHP-Einstellungen entsprechend den Anforderungen Ihres Projekts zu konfigurieren.
- Wenn Sie PHP-Versionen wechseln, stellen Sie sicher, dass alle relevanten PHP-Erweiterungen für die jeweilige Version installiert sind, die Sie verwenden möchten.
- Starten Sie Ihren Webserver (Apache, Nginx usw.) nach dem Wechsel der PHP-Versionen oder dem Aktualisieren von Konfigurationen neu, um die Änderungen zu übernehmen.
Guides
Anleitungen
Flight PHP ist so konzipiert, dass es einfach und doch leistungsstark ist, und unsere Anleitungen helfen Ihnen, reale Anwendungen schrittweise zu erstellen. Diese praktischen Tutorials führen Sie durch vollständige Projekte, um zu demonstrieren, wie Flight effektiv eingesetzt werden kann.
Offizielle Anleitungen
Erstellen eines Blogs
Lernen Sie, wie Sie eine funktionale Blog-Anwendung mit Flight PHP erstellen. Diese Anleitung führt Sie durch:
- Einrichten einer Projektstruktur
- Arbeiten mit Vorlagen unter Verwendung von Latte
- Implementieren von Routen für Beiträge
- Speichern und Abrufen von Daten
- Behandlung von Formularübermittlungen
- Grundlegende Fehlerbehandlung
Dieses Tutorial eignet sich perfekt für Anfänger, die sehen möchten, wie alle Teile in einer realen Anwendung zusammenpassen.
Einheitstests und SOLID-Prinzipien
Diese Anleitung behandelt die Grundlagen der Einheitstests in Flight PHP-Anwendungen. Dazu gehören:
- Einrichten von PHPUnit
- Schreiben von testbarem Code unter Verwendung der SOLID-Prinzipien
- Simulieren von Abhängigkeiten
- Häufige Fehlerfallen, die vermieden werden sollten
- Skalieren Ihrer Tests, wenn Ihre Anwendung wächst Dieses Tutorial ist ideal für Entwickler, die die Codequalität und -wartbarkeit verbessern möchten.
Unoffizielle Anleitungen
Obwohl diese Anleitungen nicht offiziell vom Flight-Team gepflegt werden, sind sie wertvolle Ressourcen, die von der Community erstellt wurden. Sie decken verschiedene Themen und Anwendungsfälle ab und bieten zusätzliche Einblicke in die Nutzung von Flight PHP.
Erstellen einer RESTful API mit Flight Framework
Diese Anleitung führt Sie durch das Erstellen einer RESTful API unter Verwendung des Flight PHP-Frameworks. Sie behandelt die Grundlagen der API-Einrichtung, Definieren von Routen und Rückgabe von JSON-Antworten.
Erstellen eines einfachen Blogs
Diese Anleitung führt Sie durch das Erstellen eines grundlegenden Blogs unter Verwendung des Flight PHP-Frameworks. Es gibt tatsächlich zwei Teile: Einen für die Grundlagen und einen weiteren für fortgeschrittene Themen und Verbesserungen für einen produktionsreifen Blog.
- Erstellen eines einfachen Blogs mit Flight - Teil 1 - Einstieg in einen einfachen Blog.
- Erstellen eines einfachen Blogs mit Flight - Teil 2 - Verfeinern des Blogs für die Produktion.
Erstellen einer Pokémon API in PHP: Ein Leitfaden für Anfänger
Diese unterhaltsame Anleitung führt Sie durch das Erstellen einer einfachen Pokémon API unter Verwendung von Flight PHP. Sie behandelt die Grundlagen der API-Einrichtung, Definieren von Routen und Rückgabe von JSON-Antworten.
Beitrag leisten
Haben Sie eine Idee für eine Anleitung? Einen Fehler gefunden? Wir freuen uns über Beiträge! Unsere Anleitungen werden im FlightPHP-Dokumentationsrepository gepflegt.
Falls Sie etwas Interessantes mit Flight erstellt haben und es als Anleitung teilen möchten, reichen Sie bitte einen Pull-Request ein. Das Teilen Ihres Wissens hilft der Flight-Community zu wachsen.
Suche nach API-Dokumentation?
Falls Sie spezifische Informationen zu Flights Kernfunktionen und Methoden suchen, schauen Sie in den Learn-Bereich unserer Dokumentation.