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

Nachteile im Vergleich zu Flight

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:

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:

  1. .env / echte Umgebung — Geheimnisse und Deployment-Überschreibungen (gitignoriert).
  2. app/config/config.php — Literale PHP-Array-Standardwerte (kopiert aus config_sample.php). Bevorzugen Sie keine $_ENV[...]-Ausdrücke in dieser Datei: Tools wie runway config:set könnten sie als statische Werte umschreiben und Geheimnisse in die Datei backen.
  3. Zusammenführen beim Bootstrap — Umgebung gewinnt für zugeordnete Schlüssel; Anwendungscode liest ein Konfigurationsobjekt oder $app->get(), nicht $_ENV in 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

Fehlerbehebung

Änderungsprotokoll

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:

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 bevorzuge App\…-Klassen, Konstruktorinjektion und $this->app gegenü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:

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:

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

Siehe auch

Fehlerbehebung

Änderungsprotokoll

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, wann­ever wir eine datums-/zeitbezogene Funktion aufrufen. Hier sind einige empfohlene Einstellungen:

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:

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:

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: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// 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

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

Fehlerbehebung

Changelog

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\Engine immer 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

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.

// 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:

  1. Du kannst die Eigenschaft executedRoute auf dem Objekt Flight::router() verwenden.
  2. Du kannst darum bitten, dass das Routenobjekt an deinen Callback übergeben wird, indem du true als 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 executedRoute wird nur gesetzt, nachdem eine Route ausgeführt wurde. Wenn du versuchst, darauf vor der Ausführung einer Route zuzugreifen, ist sie NULL. Du kannst executedRoute auch 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 runway anzeigen, indem du php runway routes ausfü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_buffering auf false gesetzt 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

Fehlerbehebung

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

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. $app und $this->app in einem Controller/Middleware sind der empfohlene Ansatz des Flight-Teams (und worauf das offizielle Skelett + AGENTS.md fü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:

Grundlegende Verwendung

PHPUnit einrichten

  1. PHPUnit mit Composer installieren:
    composer require --dev phpunit/phpunit
  2. Erstellen Sie ein tests-Verzeichnis im Stammverzeichnis Ihres Projekts.
  3. Fügen Sie ein Test-Skript zu Ihrer composer.json hinzu:
    "scripts": {
        "test": "phpunit --configuration phpunit.xml"
    }
  4. 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:

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

Siehe auch

Fehlerbehebung

Änderungsprotokoll

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

Nachteile im Vergleich zu Flight

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: PdoWrapper ist 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

Fehlerbehebung

Änderungsprotokoll

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

Fehlerbehebung

Änderungsprotokoll

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 kein after()-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:

  1. Sie können false von der Middleware-Funktion zurückgeben, und Flight gibt automatisch einen 403 Forbidden-Fehler zurück, aber ohne Anpassung.
  2. Sie können den Benutzer auf eine Login-Seite umleiten mit Flight::redirect().
  3. 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

Fehlerbehebung

Änderungsprotokoll

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 map und register können nicht gefiltert werden, da sie direkt aufgerufen und nicht dynamisch aufgerufen werden. Siehe Erweiterung von Flight für weitere Informationen.

Siehe auch

Fehlerbehebung

Changelog

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 $_ENV verfü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:

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 Methode null zurück. Wenn kein Accept-Header definiert ist, gibt die Methode den ersten Typ im $availableTypes-Array zurück.

Siehe auch

Fehlerbehebung

Changelog

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:

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:

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:

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 Flags JSON_THROW_ON_ERROR und JSON_UNESCAPED_SLASHES beim 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önnen exit oder return nach dem Aufruf von Flight::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

Fehlerbehebung

Changelog

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:

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:

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

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

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!.

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?

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();

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();

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!";
});

Best Practice für Flight

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

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

Siehe auch

Fehlerbehebung

Änderungsprotokoll

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

Fehlerbehebung

Änderungsprotokoll

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_MODE nicht angeben, setzt SimplePdo es automatisch auf PDO::FETCH_ASSOC fü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

SimplePdo fügt automatisch LIMIT 1 zu fetchRow()-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, gibt rowCount() 0 zurück. Dies unterscheidet sich vom Verhalten von MySQL bei der Verwendung von PDO::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:

  1. Aktualisieren Sie Ihre Registrierung:

    // Old
    Flight::register('db', \flight\database\PdoWrapper::class, [ /* ... */ ]);
    
    // New
    Flight::register('db', \flight\database\SimplePdo::class, [ /* ... */ ]);
  2. Alle bestehenden PdoWrapper-Methoden funktionieren in SimplePdo - Es gibt keine Breaking Changes. Ihr bestehender Code wird weiterhin funktionieren.

  3. Optional die neuen Hilfsmethoden verwenden - Beginnen Sie mit insert(), update(), delete() und transaction(), um Ihren Code zu vereinfachen.

Siehe auch

Fehlerbehebung

Changelog

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:

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

Fehlerbehebung

Änderungsprotokoll

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

Nachteile im Vergleich zu Flight

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:

  1. 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.
  2. 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

Fehlerbehebung

Änderungsprotokoll

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:

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

Fehlerbehebung

Änderungsprotokoll

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

Nachteile im Vergleich zu Flight

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:

  1. Composer PSR-4 (was das offizielle Grundgerüst verwendet): Ordnen Sie ein Namespace-Präfix einem Verzeichnis in composer.json zu und führen Sie dann composer dump-autoload aus.
  2. 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\HomeControllerapp/Controller/HomeController.php App\Controller\… mit Ordner app/controllers/
app\controllers\MyControllerapp/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:

Ä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

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

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:

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

Fehlerbehebung

Changelog

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

  1. 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.
  2. 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 $app aus $app = Flight::app(); zu verwenden. $app hat alle Methoden, die auch Flight:: 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.
  3. 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.
  4. Verwenden Sie aussagekräftige Namen: Testnamen sollten das zu testende Verhalten klar beschreiben. Das verbessert Lesbarkeit und Wartbarkeit.
  5. 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 $_SESSION nicht direkt in Ihrem Code auf, da dies eine globale Variable in Ihren Code einbringt und das Testen erschwert.
  6. 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.
  7. 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.
  8. 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).
  9. Verwenden Sie Controller für Routen: Verwenden Sie in Ihren Routendefinitionen Controller und keine Closures. Das flight\Engine $app wird 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.
  10. 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.
  11. 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.

  1. Führen Sie in Ihrem Projektverzeichnis aus:

    composer require --dev phpunit/phpunit

    Dies installiert die neueste PHPUnit-Version als Entwicklungsabhängigkeit.

  2. Erstellen Sie ein tests-Verzeichnis im Projektstamm für Testdateien.

  3. Fügen Sie composer.json ein Testskript hinzu, der Einfachheit halber:

    // weiterer Inhalt von composer.json
    "scripts": {
        "test": "phpunit --configuration phpunit.xml"
    }
  4. 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:

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:

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:

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

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

Schritt 1: Projekt einrichten

Beginnen Sie mit der Erstellung eines neuen Projektverzeichnisses und der Installation von Flight über Composer.

  1. Verzeichnis erstellen:

    mkdir flight-blog
    cd flight-blog
  2. Flight installieren:

    composer require flightphp/core
  3. Ein öffentliches Verzeichnis erstellen: Flight verwendet einen einzigen Einstiegspunkt (index.php). Erstellen Sie einen public/-Ordner dafür:

    mkdir public
  4. Basis-index.php: Erstellen Sie public/index.php mit einer einfachen „Hallo-Welt“-Route:

    <?php
    require '../vendor/autoload.php';
    
    Flight::route('/', function () {
        echo 'Hello, Flight!';
    });
    
    Flight::start();
  5. 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

Schritt 3: Latte installieren und konfigurieren

Latte ist eine leichtgewichtige Template-Engine, die sich gut in Flight integrieren lässt.

  1. Latte installieren:

    composer require latte/latte
  2. 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();
  3. 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>&copy; {date('Y')} Flight Blog</p>
        </footer>
    </body>
    </html>
  4. 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.

  5. 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.

  1. routes.php erstellen: In app/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']);
    });
  2. index.php aktualisieren: 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.

  1. Eine Posts-Methode hinzufügen: Fügen Sie in index.php eine Methode zum Laden von Beiträgen hinzu:

    Flight::map('posts', function () {
        $file = __DIR__ . '/../data/posts.json';
        return json_decode(file_get_contents($file), true);
    });
  2. 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.

  1. 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.

  1. 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}
  2. 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('/');
    });
  3. 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.

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

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?

Video-Übersicht

Ziemlich einfach, oder?
Erfahren Sie mehr über Flight in der Dokumentation!

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):

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.phpconfig.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

Matrix

Und Discord

Mitwirken

Es gibt zwei Möglichkeiten, wie Sie zu Flight beitragen können:

  1. Tragen Sie zum Kern-Framework bei, indem Sie das Core-Repository besuchen.
  2. 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

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:

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:

  1. Header: Metadaten über den Token (Algorithmus, Typ)
  2. Payload: Ihre Daten (Benutzer-ID, Rollen, Ablaufdatum usw.)
  3. Signature: Kryptografische Signatur zur Überprüfung der Authentizität

Beispiel-JWT: eyJ0eXAiOiJKV1QiLCJhbGc... (sieht aus wie Kauderwelsch, ist aber strukturierte Daten!)

Warum JWT verwenden?

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)

$jwt = JWT::encode($payload, $secretKey, 'HS256');
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));

Asymmetrische Algorithmen (RSA/ECDSA)

// 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"

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:

Warum diese Bibliothek verwenden?

Siehe auch

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?

Installation

  1. Laden Sie den Ordner flight-integration in Ihr /wp-content/plugins/-Verzeichnis hoch.
  2. Aktivieren Sie das Plugin im WordPress-Admin-Bereich (Plugins-Menü).
  3. Gehen Sie zu Einstellungen > Flight Framework, um das Plugin zu konfigurieren.
  4. Legen Sie den Pfad zum Vendor-Ordner Ihrer Flight-Installation fest (oder verwenden Sie Composer, um Flight zu installieren).
  5. Konfigurieren Sie den Pfad zu Ihrem App-Ordner und erstellen Sie die Ordnerstruktur (das Plugin kann Ihnen dabei helfen!).
  6. 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:

Wichtige Punkte

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

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

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

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:

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

Die Skripte sind in drei Gruppen unterteilt:

Das Verzeichnis der Skripte ist :

 <root dir>
     |
     +-- base.sql
     |
     +-- /migrations
              |
              +-- /up
                   |
                   +-- 00001.sql
                   +-- 00002.sql
              +-- /down
                   |
                   +-- 00000.sql
                   +-- 00001.sql

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:

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

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.db

Awesome-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

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:1025 und lesen Sie jede erfasste E-Mail in Mailpit unter http://<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:

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:

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

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:

So funktioniert es:

Tracy Debugger Integration

CommentTemplate beinhaltet Integration mit Tracy Debugger für Entwicklungs-Logging und Debugging.

Comment Template Tracy

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:

Was wird protokolliert

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

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 oder safeIdentifier().


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:

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">
            &copy; 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

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:

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:

Alle Methoden außer get() und id() geben die Session-Instanz für Kettenaufrufe zurück.

Warum dieses Plugin verwenden?

Technische Details

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.json zugunsten von app/config/config.php veraltet. Migrieren Sie mit php runway config:migrate beim Upgrade älterer Projekte. Das Skeleton kann beim Erstellen eines Projekts weiterhin eine kleine .runway-config.json schreiben, um die Kompatibilität zu gewährleisten; bevorzugen Sie den runway-Schlüssel in config.php fü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.

  1. Wenn Sie das Skeleton-Projekt verwenden, können Sie php runway [Befehl] aus dem Root Ihres Projekts ausführen.
  2. 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/ und app\controllers. Das bleibt gültig, wenn Ihr Baum weiterhin Kleinbuchstaben für Ordner verwendet. Neue Skeleton-Projekte und aktuelle make:controller-Ausgaben verwenden app/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.php als literalen PHP-Wert. Geheimnisse gehören in .env. Vermeiden Sie $_ENV[...]-Ausdrücke innerhalb von config.phpconfig:set schreibt 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

Awesome-plugins/tracy_extensions

Tracy Flight Panel-Erweiterungen

Dies ist ein Satz von Erweiterungen, um die Arbeit mit Flight etwas reichhaltiger zu gestalten.

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

Flight Bar

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

Flight Data Flight Database Flight Request

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

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

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.

FlightPHP APM

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:

Unterstützte Datenbanken

FlightPHP APM unterstützt derzeit die folgenden Datenbanken zur Speicherung von Metriken:

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?

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?

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?

Dauerhaft laufen lassen Für Live-Apps möchten Sie eine kontinuierliche Verarbeitung. Hier sind Ihre Optionen:

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?

Anpassen:

php vendor/bin/runway apm:dashboard --host 0.0.0.0 --port 8080 --php-path=/usr/local/bin/php

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:

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:

Extras:

Beispiel: Eine Anfrage an /users könnte zeigen:

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:

Achtung:

Beispielausgabe:

Worker-Optionen

Stellen Sie den Worker nach Ihren Wünschen ein:

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:

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.

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

Einschränkungen

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">
            &copy; 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.

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.

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.

Async

Flight ist bereits ein schnelles Framework, aber wenn man ihm einen Turbomotor verpasst, macht alles noch mehr Spaß (und ist herausfordernder)!

Autorisierung/Berechtigungen

Autorisierung und Berechtigungen sind für jede Anwendung entscheidend, die Kontrollen erfordert, wer auf was zugreifen kann.

Authentifizierung

Authentifizierung ist für Anwendungen unerlässlich, die Benutzeridentität überprüfen und API-Endpunkte sichern mü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.

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.

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.

Debugging

Debugging ist entscheidend, wenn Sie in Ihrer lokalen Umgebung entwickeln. Es gibt ein paar Plugins, die Ihr Debugging-Erlebnis verbessern können.

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.

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.

E-Mail

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.

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.

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.

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.

WordPress-Integration

Möchten Sie Flight in Ihrem WordPress-Projekt verwenden? Es gibt ein praktisches Plugin dafür!

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

Videos und Tutorials

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:

Community-beigetragene Beispiele:

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!

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:

  1. Installieren Sie PHP auf Ihrem System
  2. 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.phpconfig.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 unter app/ 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 nach RewriteEngine On hinzu.

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

  1. 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)"
  2. 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
  3. 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

Windows 10/11

PHP manuell installieren

  1. 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.
  2. PHP entpacken:

    • Entpacken Sie die heruntergeladene ZIP-Datei nach C:\php.
  3. 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.
  4. PHP konfigurieren:

    • Kopieren Sie php.ini-development nach php.ini.
    • Bearbeiten Sie php.ini, um PHP nach Bedarf zu konfigurieren (z. B. extension_dir festlegen, Erweiterungen aktivieren).
  5. PHP-Installation überprüfen:

    • Öffnen Sie die Eingabeaufforderung und führen Sie aus:
      php -v

Mehrere PHP-Versionen installieren

  1. Wiederholen Sie die obigen Schritte für jede Version und platzieren Sie jede in einem separaten Verzeichnis (z. B. C:\php7, C:\php8).

  2. 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

  1. Paketlisten aktualisieren:

    • Öffnen Sie das Terminal und führen Sie aus:
      sudo apt update
  2. 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
  3. Zusätzliche Module installieren (optional):

    • Um beispielsweise MySQL-Unterstützung zu installieren:
      sudo apt install php8.1-mysql
  4. Zwischen PHP-Versionen wechseln:

    • Verwenden Sie update-alternatives:
      sudo update-alternatives --set php /usr/bin/php8.1
  5. Installierte Version überprüfen:

    • Führen Sie aus:
      php -v

Rocky Linux

PHP mit yum/dnf installieren

  1. EPEL-Repository aktivieren:

    • Öffnen Sie das Terminal und führen Sie aus:
      sudo dnf install epel-release
  2. Remi-Repository installieren:

    • Führen Sie aus:
      sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
      sudo dnf module reset php
  3. 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
  4. 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
  5. Installierte Version überprüfen:

    • Führen Sie aus:
      php -v

Allgemeine Hinweise

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:

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:

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 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.