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 werden standardmäßig festgelegt, aber Sie können sie bei Bedarf überschreiben. Sie können auch eigene Variablen festlegen, die in Ihrer gesamten Anwendung verwendet werden können.

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 der Datei app/config/config.php können Sie alle standardmäßigen Konfigurationsvariablen sehen, die Ihnen zur Verfügung stehen.

Grundlegende Verwendung

Flight-Konfigurationsoptionen

Die folgende Liste enthält alle verfügbaren Konfigurationseinstellungen:

Loader-Konfiguration

Es gibt zusätzlich eine weitere Konfigurationseinstellung für den Loader. Dies ermöglicht es Ihnen, Klassen mit _ im Klassennamen automatisch zu laden.

// Aktiviere Klassenladen mit Unterstrichen
// Standardmäßig true
Loader::$v2ClassLoading = false;

Variablen

Flight ermöglicht es Ihnen, Variablen zu speichern, damit sie überall in Ihrer Anwendung verwendet werden können.

// Speichern Sie Ihre Variable
Flight::set('id', 123);

// An anderer Stelle in Ihrer Anwendung
$id = Flight::get('id');

Um zu überprü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 es schwierig macht, Fehler zu finden. Zusätzlich kann dies Dinge wie Unit-Testing komplizieren.

Fehler und Ausnahmen

Alle Fehler und Ausnahmen werden von Flight abgefangen und an die error-Methode weitergeleitet, wenn flight.handle_errors auf true gesetzt ist.

Das standardmäßige Verhalten ist das Senden einer generischen HTTP 500 Internal Server Error-Antwort mit einigen Fehlerinformationen.

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 im 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 standardmäßige Verhalten ist das Senden einer HTTP 404 Not Found-Antwort mit einer einfachen Nachricht.

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

Überblick

Flight erleichtert es, Ihre PHP-Projekte mit KI-gestützten Tools und modernen Entwickler-Workflows zu superchargen. Mit integrierten Befehlen zum Verbinden mit LLM-Anbietern (Large Language Model) und zum Generieren projektspezifischer KI-Codierungsanweisungen hilft Flight Ihnen und Ihrem Team, das Maximum aus KI-Assistenten wie GitHub Copilot, Cursor und Windsurf herauszuholen.

Verständnis

KI-Codierungsassistenten sind am hilfreichsten, wenn sie den Kontext, die Konventionen und die Ziele Ihres Projekts verstehen. Die KI-Hilfsprogramme von Flight ermöglichen es Ihnen:

Diese Funktionen sind in die Kern-CLI von Flight und das offizielle flightphp/skeleton Starter-Projekt integriert.

Grundlegende Verwendung

Einrichten von LLM-Zugangsdaten

Der Befehl ai:init führt Sie durch den Prozess, Ihr Projekt mit einem LLM-Anbieter zu verbinden.

php runway ai:init

Sie werden aufgefordert:

Dies erstellt eine .runway-creds.json-Datei im Stammverzeichnis Ihres Projekts (und stellt sicher, dass sie in Ihrer .gitignore ist).

Beispiel:

Willkommen bei AI Init!
Welchen LLM-API möchten Sie verwenden? [1] openai, [2] grok, [3] claude: 1
Geben Sie die Basis-URL für die LLM-API ein [https://api.openai.com]:
Geben Sie Ihren API-Schlüssel für openai ein: sk-...
Geben Sie den Modellnamen ein, den Sie verwenden möchten (z. B. gpt-4, claude-3-opus usw.) [gpt-4o]:
Zugangsdaten in .runway-creds.json gespeichert

Generieren projektspezifischer KI-Anweisungen

Der Befehl ai:generate-instructions hilft Ihnen, Anweisungen für KI-Codierungsassistenten zu erstellen oder zu aktualisieren, die auf Ihr Projekt zugeschnitten sind.

php runway ai:generate-instructions

Sie beantworten ein paar Fragen zu Ihrem Projekt (Beschreibung, Datenbank, Templating, Sicherheit, Teamgröße usw.). Flight verwendet Ihren LLM-Anbieter, um Anweisungen zu generieren, und schreibt sie dann in:

Beispiel:

Beschreiben Sie bitte, wofür Ihr Projekt gedacht ist? Meine tolle API
Welche Datenbank planen Sie zu verwenden? MySQL
Welchen HTML-Templating-Engine planen Sie zu verwenden (falls zutreffend)? latte
Ist Sicherheit ein wichtiger Aspekt dieses Projekts? (y/n) y
...
KI-Anweisungen erfolgreich aktualisiert.

Nun geben Ihre KI-Tools intelligentere, relevantere Vorschläge basierend auf den tatsächlichen Bedürfnissen Ihres Projekts.

Erweiterte 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

Überblick

Sicherheit ist ein großes Thema, wenn es um Webanwendungen geht. Sie wollen sicherstellen, dass Ihre Anwendung sicher ist und dass die Daten Ihrer Benutzer geschützt sind. Flight bietet eine Reihe von Funktionen, um Ihnen bei der Absicherung Ihrer Webanwendungen zu helfen.

Verständnis

Es gibt eine Reihe gängiger Sicherheitsbedrohungen, auf die Sie achten sollten, wenn Sie Webanwendungen entwickeln. Einige der häufigsten Bedrohungen umfassen:

Templates helfen bei XSS, indem sie die Ausgabe standardmäßig escapen, sodass Sie sich nicht daran erinnern müssen, das zu tun. Sessions können bei CSRF helfen, indem sie ein CSRF-Token in der Benutzersitzung speichern, wie unten beschrieben. Die Verwendung vorbereiteter Anweisungen mit PDO kann SQL-Injection-Angriffe verhindern (oder die bequemen Methoden in der PdoWrapper-Klasse). CORS kann mit einem einfachen Hook gehandhabt werden, bevor Flight::start() aufgerufen wird.

All diese Methoden arbeiten zusammen, um Ihre Webanwendungen sicher zu halten. Es sollte immer im Vordergrund Ihres Geistes stehen, Sicherheitsbest Practices zu lernen und zu verstehen.

Grundlegende Verwendung

Header

HTTP-Header sind eine der einfachsten Möglichkeiten, Ihre Webanwendungen abzusichern. Sie können Header verwenden, um Clickjacking, XSS und andere Angriffe zu verhindern. Es gibt mehrere Möglichkeiten, diese Header zu Ihrer Anwendung hinzuzufügen.

Zwei großartige Websites, um die Sicherheit Ihrer Header zu überprüfen, sind securityheaders.com und observatory.mozilla.org. Nachdem Sie den unten stehenden Code eingerichtet haben, können Sie leicht überprüfen, ob Ihre Header mit diesen zwei Websites funktionieren.

Manuell hinzufügen

Sie können diese Header manuell hinzufügen, indem Sie die header-Methode auf dem Flight\Response-Objekt verwenden.

// 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 möchten Sie
//  Beispiele im Internet für Ihre 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 viel 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 Features und APIs verwendet werden können
Flight::response()->header('Permissions-Policy', 'geolocation=()');

Diese können oben in Ihren routes.php- oder index.php-Dateien hinzugefügt werden.

Als Filter hinzufügen

Sie können sie auch in einem Filter/Hook wie dem Folgenden 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

Sie können sie auch als Middleware-Klasse hinzufügen, die die größte Flexibilität bietet, für welche Routen dies angewendet werden soll. Im Allgemeinen sollten diese Header auf alle HTML- und API-Antworten angewendet werden.

// app/middlewares/SecurityHeadersMiddleware.php

namespace app\middlewares;

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();
        $response->header('X-Frame-Options', 'SAMEORIGIN');
        $response->header("Content-Security-Policy", "default-src 'self'");
        $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=()');
    }
}

// index.php oder wo immer Sie Ihre Routen haben
// FYI: Diese leere String-Gruppe wirkt als globales Middleware für
// alle Routen. Natürlich könnten Sie dasselbe tun und dies nur zu
// spezifischen Routen hinzufügen.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // mehr Routen
}, [ SecurityHeadersMiddleware::class ]);

Cross Site Request Forgery (CSRF)

Cross Site Request Forgery (CSRF) ist ein Angriffstyp, bei dem eine bösartige Website den Browser eines Benutzers dazu bringen kann, eine Anfrage an Ihre Website zu senden. Dies kann verwendet werden, um Aktionen auf Ihrer Website ohne das Wissen des Benutzers auszuführen. Flight bietet keinen integrierten CSRF-Schutzmechanismus, aber Sie können Ihren eigenen leicht mit Middleware implementieren.

Einrichtung

Zuerst müssen Sie ein CSRF-Token generieren und es in der Benutzersitzung speichern. Sie können dann dieses Token in Ihren Formularen verwenden und es überprüfen, wenn das Formular abgeschickt wird. Wir verwenden das flightphp/session-Plugin, um Sitzungen zu verwalten.

// Generiere ein CSRF-Token und speichere es in der Benutzersitzung
// (angenommen, Sie haben ein Session-Objekt erstellt und es an Flight angehängt)
// siehe die Session-Dokumentation für weitere Informationen
Flight::register('session', flight\Session::class);

// Sie müssen nur ein einzelnes 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)) );
}
Verwendung der standardmäßigen PHP Flight Template
<!-- 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>
Verwendung von Latte

Sie können auch eine benutzerdefinierte Funktion setzen, um das CSRF-Token in Ihren Latte-Templates auszugeben.


Flight::map('render', function(string $template, array $data, ?string $block): void {
    $latte = new Latte\Engine;

    // andere 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 können Sie in Ihren Latte-Templates die csrf()-Funktion verwenden, um das CSRF-Token auszugeben.

<form method="post">
    {csrf()}
    <!-- andere Formularfelder -->
</form>

CSRF-Token überprüfen

Sie können das CSRF-Token mit mehreren Methoden überprüfen.

Middleware
// app/middlewares/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, 'Ungültiges CSRF-Token');
            }
        }
    }
}

// index.php oder wo immer Sie Ihre Routen haben
use app\middlewares\CsrfMiddleware;

Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // mehr Routen
}, [ CsrfMiddleware::class ]);
Event-Filter
// Dieses Middleware überprüft, ob die Anfrage eine POST-Anfrage ist und ob sie es ist, überprüft es, 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, 'Ungültiges CSRF-Token');
            // oder für eine JSON-Antwort
            Flight::jsonHalt(['error' => 'Ungültiges CSRF-Token'], 403);
        }
    }
});

Cross Site Scripting (XSS)

Cross Site Scripting (XSS) ist ein Angriffstyp, bei dem eine bösartige Formulareingabe Code in Ihre Website injizieren kann. Die meisten dieser Möglichkeiten stammen von Formularwerten, die Ihre Endbenutzer ausfüllen werden. Sie sollten niemals der Ausgabe Ihrer Benutzer vertrauen! Nehmen Sie immer an, dass alle von ihnen die besten Hacker der Welt sind. Sie können bösartigen JavaScript- oder HTML-Code in Ihre Seite injizieren. Dieser Code kann verwendet werden, um Informationen von Ihren Benutzern zu stehlen oder Aktionen auf Ihrer Website auszuführen. Mit der View-Klasse von Flight oder einem anderen Templating-Engine wie Latte können Sie 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 ausgeben: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Wenn Sie etwas wie Latte als Ihre View-Klasse registriert haben, wird es dies auch automatisch escapen.
Flight::view()->render('template', ['name' => $name]);

SQL Injection

SQL Injection ist ein Angriffstyp, bei dem ein bösartiger Benutzer SQL-Code in Ihre Datenbank injizieren kann. Dies kann verwendet werden, um Informationen aus Ihrer Datenbank zu stehlen oder Aktionen auf Ihrer Datenbank auszuführen. Wiederum sollten Sie niemals Eingaben von Ihren Benutzern vertrauen! Nehmen Sie immer an, dass sie blutrünstig sind. Die Verwendung vorbereiteter Anweisungen in Ihren PDO-Objekten wird SQL-Injection verhindern.

// Angenommen, Sie haben Flight::db() als Ihr PDO-Objekt registriert
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();

// Wenn Sie die PdoWrapper-Klasse verwenden, kann dies leicht in einer Zeile erledigt werden
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

// Sie können dasselbe mit einem PDO-Objekt mit ?-Platzhaltern tun
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);

Unsicheres Beispiel

Das Folgende ist der Grund, warum wir SQL-vorbereitete Anweisungen verwenden, um vor unschuldigen Beispielen wie dem unten zu schützen:

// Endbenutzer füllt ein Webformular aus.
// Für den Wert des Formulars gibt der Hacker etwas wie dies ein:
$username = "' OR 1=1; -- ";

$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// Nachdem die Abfrage aufgebaut ist, 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 funktioniert. Tatsächlich
// ist es ein sehr häufiger SQL-Injection-Angriff, der alle Benutzer zurückgibt.

var_dump($users); // dies wird alle Benutzer in der Datenbank ausgeben, nicht nur den einen einzelnen Benutzernamen

CORS

Cross-Origin Resource Sharing (CORS) ist ein Mechanismus, der es vielen Ressourcen (z. B. Schriftarten, JavaScript usw.) auf einer Webseite ermöglicht, von einer anderen Domain außerhalb der Domain angefordert zu werden, von der die Ressource stammt. Flight hat keine integrierte Funktionalität, aber dies kann leicht mit einem Hook gehandhabt werden, der vor dem Aufruf der Flight::start()-Methode ausgeführt wird.

// app/utils/CorsUtil.php

namespace app\utils;

class CorsUtil
{
    public function set(array $params): void
    {
        $request = Flight::request();
        $response = Flight::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
    {
        // Passen Sie Ihre erlaubten Hosts hier an.
        $allowed = [
            'capacitor://localhost',
            'ionic://localhost',
            'http://localhost',
            'http://localhost:4200',
            'http://localhost:8080',
            'http://localhost:8100',
        ];

        $request = Flight::request();

        if (in_array($request->getVar('HTTP_ORIGIN'), $allowed, true) === true) {
            $response = Flight::response();
            $response->header("Access-Control-Allow-Origin", $request->getVar('HTTP_ORIGIN'));
        }
    }
}

// index.php oder wo immer Sie Ihre Routen haben
$CorsUtil = new CorsUtil();

// Dies muss vor dem Ausführen von start ausgeführt werden.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Fehlerbehandlung

Verbergen Sie sensible Fehlerdetails in der Produktion, um das Austreten von Informationen an Angreifer zu vermeiden. In der Produktion protokollieren Sie Fehler anstatt sie anzuzeigen, mit display_errors auf 0 gesetzt.

// In Ihrer bootstrap.php oder index.php

// Fügen Sie dies zu Ihrer app/config/config.php hinzu
$environment = ENVIRONMENT;
if ($environment === 'production') {
    ini_set('display_errors', 0); // Deaktiviere Fehlanzeige
    ini_set('log_errors', 1);     // Protokolliere Fehler stattdessen
    ini_set('error_log', '/path/to/error.log');
}

// In Ihren Routen oder Controllern
// Verwenden Sie Flight::halt() für kontrollierte Fehlerantworten
Flight::halt(403, 'Zugriff verweigert');

Eingabe-Sanitization

Vertrauen Sie niemals Benutzereingaben. Sanitieren Sie sie mit filter_var, bevor Sie sie verarbeiten, um zu verhindern, dass bösartige Daten eindringen.


// Nehmen wir an, eine $_POST-Anfrage mit $_POST['input'] und $_POST['email']

// Sanitisiere eine String-Eingabe
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Sanitisiere eine E-Mail
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);

Passwort-Hashing

Speichern Sie Passwörter sicher und verifizieren Sie sie sicher mit PHPs integrierten Funktionen wie password_hash und password_verify. Passwörter sollten niemals im Klartext gespeichert werden, noch sollten sie mit reversiblen Methoden verschlüsselt werden. Hashing stellt sicher, dass selbst wenn Ihre Datenbank kompromittiert wird, die tatsächlichen Passwörter geschützt bleiben.

$password = Flight::request()->data->password;
// Hash ein Passwort beim Speichern (z. B. während der Registrierung)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// Verifiziere ein Passwort (z. B. während des Logins)
if (password_verify($password, $stored_hash)) {
    // Passwort stimmt überein
}

Ratenbegrenzung

Schützen Sie vor Brute-Force-Angriffen oder Denial-of-Service-Angriffen, indem Sie Anfrageraten mit einem Cache begrenzen.

// Angenommen, Sie haben 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, 'Zu viele Anfragen');
    }

    $cache->set($key, $attempts + 1, 60); // Zurücksetzen nach 60 Sekunden
});

Siehe auch

Fehlerbehebung

Änderungsprotokoll

Learn/routing

Routing

Übersicht

Routing in Flight PHP ordnet URL-Muster Callback-Funktionen oder Klassenmethoden zu, um schnelle und einfache Anfragenverarbeitung zu ermöglichen. Es ist für minimale Overhead, benutzerfreundliche Nutzung für Anfänger und Erweiterbarkeit ohne externe Abhängigkeiten konzipiert.

Verständnis

Routing ist der Kernmechanismus, der HTTP-Anfragen mit der Anwendungslogik in Flight verbindet. Durch das Definieren von Routen spezifizieren Sie, wie verschiedene URLs spezifischen 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 Ihren Code organisiert und leicht wartbar, während er für Anfänger schnell und einfach bleibt und für fortgeschrittene Benutzer erweiterbar ist.

Hinweis: Möchten Sie mehr über Routing erfahren? Schauen Sie auf der Seite "why a framework?" für eine detailliertere Erklärung nach.

Grundlegende Nutzung

Definieren einer einfachen Route

Grundlegendes Routing in Flight erfolgt durch das Abgleichen eines URL-Musters mit einer Callback-Funktion oder einem Array aus einer Klasse und Methode.

Flight::route('/', function(){
    echo 'hello world!';
});

Routen werden in der Reihenfolge abgeglichen, in der sie definiert werden. Die erste Route, die zu einer Anfrage passt, wird ausgeführt.

Verwendung von Funktionen als Callbacks

Der Callback kann jedes aufrufbare Objekt sein. Sie können also eine normale Funktion verwenden:

function hello() {
    echo 'hello world!';
}

Flight::route('/', 'hello');

Verwendung von Klassen und Methoden als Controller

Sie können auch eine Methode (statisch oder nicht) 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 Sie zuerst ein Objekt erstellen und dann die Methode aufrufen:

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 beim Aufruf eines Controllers im Framework die Klasse flight\Engine immer injiziert, es sei denn, Sie spezifizieren es über einen Dependency Injection Container.

Methode-spezifisches Routing

Standardmäßig werden Routenmuster gegen alle Anfragemethoden abgeglichen. Sie können auf spezifische Methoden reagieren, indem Sie einen Bezeichner vor die URL stellen.

Flight::route('GET /', function () {
  echo 'I received a GET request.';
});

Flight::route('POST /', function () {
  echo 'I received a POST request.';
});

// Sie können Flight::get() nicht für Routen verwenden, da dies eine Methode ist,
// um Variablen zu erhalten, nicht um eine Route zu erstellen.
Flight::post('/', function() { /* code */ });
Flight::patch('/', function() { /* code */ });
Flight::put('/', function() { /* code */ });
Flight::delete('/', function() { /* code */ });

Sie können auch mehrere Methoden auf einen einzigen Callback abbilden, indem Sie den |-Trenner verwenden:

Flight::route('GET|POST /', function () {
  echo 'I received either a GET or a POST request.';
});

Spezielle Behandlung für HEAD- und OPTIONS-Anfragen

Flight bietet integrierte Behandlung für HEAD- und OPTIONS-HTTP-Anfragen:

HEAD-Anfragen

Flight::route('GET /info', function() {
    echo 'This is some info!';
});
// Eine HEAD-Anfrage an /info gibt dieselben Header zurück, aber keinen Body.

OPTIONS-Anfragen

OPTIONS-Anfragen werden automatisch von Flight für jede definierte Route behandelt.

// Für eine Route, die als:
Flight::route('GET|POST /users', function() { /* ... */ });

// Eine OPTIONS-Anfrage an /users antwortet mit:
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS

Verwendung des Router-Objekts

Zusätzlich können Sie das Router-Objekt abrufen, das einige Hilfsmethoden für Sie bietet:


$router = Flight::router();

// mapped alle Methoden 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)

Sie können reguläre Ausdrücke in Ihren Routen verwenden:

Flight::route('/user/[0-9]+', function () {
  // Dies passt zu /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 einfacher zu warten sind.

Benannte Parameter

Sie können benannte Parameter in Ihren Routen spezifizieren, die an Ihre Callback-Funktion weitergegeben werden. Dies dient hauptsächlich der Lesbarkeit der Route. Bitte lesen Sie den Abschnitt unten zu wichtigen Einschränkungen.

Flight::route('/@name/@id', function (string $name, string $id) {
  echo "hello, $name ($id)!";
});

Sie können auch reguläre Ausdrücke mit Ihren benannten Parametern kombinieren, indem Sie den :-Trenner verwenden:

Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
  // Dies passt zu /bob/123
  // Passt aber nicht zu /bob/12345
});

Hinweis: Das Abgleichen von Regex-Gruppen () mit positionsbasierten Parametern wird nicht unterstützt. Beispiel: :'\(

Wichtige Einschränkung

Während im obigen Beispiel @name direkt mit der Variable $name verknüpft zu sein scheint, ist das nicht der Fall. Die Reihenfolge der Parameter in der Callback-Funktion bestimmt, was weitergegeben wird. Wenn Sie die Reihenfolge der Parameter in der Callback-Funktion umkehren würden, würden auch die Variablen umgekehrt. Hier ein Beispiel:

Flight::route('/@name/@id', function (string $id, string $name) {
  echo "hello, $name ($id)!";
});

Und wenn Sie zur folgenden URL gehen: /bob/123, wäre die Ausgabe hello, 123 (bob)!. Seien Sie vorsichtig, wenn Sie Ihre Routen und Callback-Funktionen einrichten!

Optionale Parameter

Sie können benannte Parameter spezifizieren, die optional für das Abgleichen sind, indem Sie Segmente in Klammern setzen.

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // Dies passt zu den folgenden URLs:
    // /blog/2012/12/10
    // /blog/2012/12
    // /blog/2012
    // /blog
  }
);

Optionale Parameter, die nicht abgeglichen werden, werden als NULL weitergegeben.

Wildcard-Routing

Das Abgleichen erfolgt nur auf einzelne URL-Segmente. Wenn Sie mehrere Segmente abgleichen möchten, können Sie den *-Wildcard verwenden.

Flight::route('/blog/*', function () {
  // Dies passt zu /blog/2000/02/01
});

Um alle Anfragen an einen einzigen Callback zu routen, können Sie Folgendes tun:

Flight::route('*', function () {
  // Etwas tun
});

404 Not Found Handler

Standardmäßig sendet Flight bei einer nicht gefundenen URL eine sehr einfache und schlichte HTTP 404 Not Found-Antwort. Wenn Sie eine personalisiertere 404-Antwort haben möchten, können Sie Ihre eigene notFound-Methode mappen:

Flight::map('notFound', function() {
    $url = Flight::request()->url;

    // Sie könnten auch Flight::render() mit einer benutzerdefinierten Vorlage verwenden.
    $output = <<<HTML
        <h1>My Custom 404 Not Found</h1>
        <h3>The page you have requested {$url} could not be found.</h3>
        HTML;

    $this->response()
        ->clearBody()
        ->status(404)
        ->write($output)
        ->send();
});

Method Not Found Handler

Standardmäßig sendet Flight bei einer gefundenen URL, aber nicht erlaubten Methode, eine sehr einfache und schlichte HTTP 405 Method Not Allowed-Antwort (z. B. Method Not Allowed. Allowed Methods are: GET, POST). Es enthält auch einen Allow-Header mit den erlaubten Methoden für diese URL.

Wenn Sie eine personalisiertere 405-Antwort haben möchten, können Sie Ihre eigene methodNotFound-Methode mappen:

use flight\net\Route;

Flight::map('methodNotFound', function(Route $route) {
    $url = Flight::request()->url;
    $methods = implode(', ', $route->methods);

    // Sie könnten auch Flight::render() mit einer benutzerdefinierten Vorlage verwenden.
    $output = <<<HTML
        <h1>My Custom 405 Method Not Allowed</h1>
        <h3>The method you have requested for {$url} is not allowed.</h3>
        <p>Allowed Methods are: {$methods}</p>
        HTML;

    $this->response()
        ->clearBody()
        ->status(405)
        ->setHeader('Allow', $methods)
        ->write($output)
        ->send();
});

Erweiterte Nutzung

Dependency Injection in Routen

Wenn Sie Dependency Injection über einen Container (PSR-11, PHP-DI, Dice usw.) verwenden möchten, ist der einzige Routentyp, bei dem dies verfügbar ist, entweder das direkte Erstellen des Objekts selbst und die Verwendung des Containers, um Ihr Objekt zu erstellen, oder Sie können Strings verwenden, um die Klasse und Methode zum Aufrufen zu definieren. Sie können zur Seite Dependency Injection für weitere Informationen gehen.

Hier ein kurzes Beispiel:


use flight\database\PdoWrapper;

// Greeting.php
class Greeting
{
    protected PdoWrapper $pdoWrapper;
    public function __construct(PdoWrapper $pdoWrapper) {
        $this->pdoWrapper = $pdoWrapper;
    }

    public function hello(int $id) {
        // do something with $this->pdoWrapper
        $name = $this->pdoWrapper->fetchField("SELECT name FROM users WHERE id = ?", [ $id ]);
        echo "Hello, world! My name is {$name}!";
    }
}

// index.php

// Setup the container with whatever params you need
// See the Dependency Injection page for more information on PSR-11
$dice = new \Dice\Dice();

// Don't forget to reassign the variable with '$dice = '!!!!!
$dice = $dice->addRule('flight\database\PdoWrapper', [
    'shared' => true,
    'constructParams' => [ 
        'mysql:host=localhost;dbname=test', 
        'root',
        'password'
    ]
]);

// Register the container handler
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
});

// Routes like normal
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// or
Flight::route('/hello/@id', 'Greeting->hello');
// or
Flight::route('/hello/@id', 'Greeting::hello');

Flight::start();

Ausführung an nächste Route weitergeben

Veraltet Sie können die Ausführung an die nächste passende Route weitergeben, indem Sie true aus Ihrer Callback-Funktion zurückgeben.

Flight::route('/user/@name', function (string $name) {
  // Check some condition
  if ($name !== "Bob") {
    // Continue to next route
    return true;
  }
});

Flight::route('/user/*', function () {
  // This will get called
});

Es wird nun empfohlen, Middleware für komplexe Anwendungsfälle wie diesen zu verwenden.

Route-Aliasing

Durch das Zuweisen eines Aliases zu einer Route können Sie diesen Alias später dynamisch in Ihrer App aufrufen, um ihn später in Ihrem Code zu generieren (z. B. ein Link in einer HTML-Vorlage oder das Generieren einer Redirect-URL).

Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
// or 
Flight::route('/users/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');

// later in code somewhere
class UserController {
    public function update() {

        // code to save user...
        $id = $user['id']; // 5 for example

        $redirectUrl = Flight::getUrl('user_view', [ 'id' => $id ]); // will return '/users/5'
        Flight::redirect($redirectUrl);
    }
}

Dies ist besonders hilfreich, wenn sich Ihre URL ändert. Im obigen Beispiel, sagen wir, dass Benutzer zu /admin/users/@id verschoben wurden. Mit Aliasing vorhanden für die Route müssen Sie nicht mehr alle alten URLs in Ihrem Code finden und ändern, da der Alias nun /admin/users/5 zurückgibt, wie im obigen Beispiel.

Route-Aliasing funktioniert auch in Gruppen:

Flight::group('/users', function() {
    Flight::route('/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
    // or
    Flight::route('/@id', function($id) { echo 'user:'.$id; })->setAlias('user_view');
});

Überprüfen von Routeninformationen

Wenn Sie die passende Routeninformation überprüfen möchten, gibt es 2 Wege, dies zu tun:

  1. Sie können die executedRoute-Eigenschaft auf dem Flight::router()-Objekt verwenden.
  2. Sie können das Routenobjekt anfordern, das an Ihren Callback weitergegeben wird, indem Sie true als dritten Parameter in der Routenmethode übergeben. Das Routenobjekt wird immer der letzte Parameter sein, der an Ihre Callback-Funktion weitergegeben wird.

executedRoute

Flight::route('/', function() {
  $route = Flight::router()->executedRoute;
  // Do something with $route
  // Array of HTTP methods matched against
  $route->methods;

  // Array of named parameters
  $route->params;

  // Matching regular expression
  $route->regex;

  // Contains the contents of any '*' used in the URL pattern
  $route->splat;

  // Shows the url path....if you really need it
  $route->pattern;

  // Shows what middleware is assigned to this
  $route->middleware;

  // Shows the alias assigned to this route
  $route->alias;
});

Hinweis: Die executedRoute-Eigenschaft wird nur gesetzt, nachdem eine Route ausgeführt wurde. Wenn Sie versuchen, darauf zuzugreifen, bevor eine Route ausgeführt wurde, ist sie NULL. Sie können executedRoute auch in Middleware verwenden!

true in Routendefinition übergeben

Flight::route('/', function(\flight\net\Route $route) {
  // Array of HTTP methods matched against
  $route->methods;

  // Array of named parameters
  $route->params;

  // Matching regular expression
  $route->regex;

  // Contains the contents of any '*' used in the URL pattern
  $route->splat;

  // Shows the url path....if you really need it
  $route->pattern;

  // Shows what middleware is assigned to this
  $route->middleware;

  // Shows the alias assigned to this route
  $route->alias;
}, true);// <-- Dieser true-Parameter ist das, was das bewirkt

Routengruppierung und Middleware

Es kann vorkommen, dass Sie verwandte Routen gruppieren möchten (z. B. /api/v1). Sie können dies tun, indem Sie die group-Methode verwenden:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // Matches /api/v1/users
  });

  Flight::route('/posts', function () {
    // Matches /api/v1/posts
  });
});

Sie können sogar Gruppen von Gruppen verschachteln:

Flight::group('/api', function () {
  Flight::group('/v1', function () {
    // Flight::get() gets variables, it doesn't set a route! See object context below
    Flight::route('GET /users', function () {
      // Matches GET /api/v1/users
    });

    Flight::post('/posts', function () {
      // Matches POST /api/v1/posts
    });

    Flight::put('/posts/1', function () {
      // Matches PUT /api/v1/posts
    });
  });
  Flight::group('/v2', function () {

    // Flight::get() gets variables, it doesn't set a route! See object context below
    Flight::route('GET /users', function () {
      // Matches GET /api/v2/users
    });
  });
});

Gruppierung mit Objektkontext

Sie können die Routengruppierung immer noch mit dem Engine-Objekt auf folgende Weise verwenden:

$app = Flight::app();

$app->group('/api/v1', function (Router $router) {

  // user the $router variable
  $router->get('/users', function () {
    // Matches GET /api/v1/users
  });

  $router->post('/posts', function () {
    // Matches POST /api/v1/posts
  });
});

Hinweis: Dies ist die bevorzugte Methode, um Routen und Gruppen mit dem $router-Objekt zu definieren.

Gruppierung mit Middleware

Sie können auch Middleware einer Gruppe von Routen zuweisen:

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // Matches /api/v1/users
  });
}, [ MyAuthMiddleware::class ]); // oder [ new MyAuthMiddleware() ], wenn Sie eine Instanz verwenden möchten

Weitere Details finden Sie auf der Seite group middleware.

Ressourcen-Routing

Sie können eine Reihe von Routen für eine Ressource mit der resource-Methode erstellen. Dies erstellt eine Reihe von Routen für eine Ressource, die den RESTful-Konventionen folgt.

Um eine Ressource zu erstellen, tun Sie Folgendes:

Flight::resource('/users', UsersController::class);

Und was im Hintergrund passiert, ist, dass es 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 Ihr 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: Sie können die neu hinzugefügten Routen mit runway anzeigen, indem Sie php runway routes ausführen.

Anpassen von Ressourcen-Routen

Es gibt einige Optionen, um die Ressourcen-Routen zu konfigurieren.

Alias Base

Sie können die aliasBase konfigurieren. Standardmäßig ist der Alias der letzte Teil der angegebenen URL. Zum Beispiel würde /users/ zu einem aliasBase von users führen. Wenn diese Routen erstellt werden, sind die Aliase users.index, users.create usw. Wenn Sie den Alias ändern möchten, setzen Sie die aliasBase auf den gewünschten Wert.

Flight::resource('/users', UsersController::class, [ 'aliasBase' => 'user' ]);
Only und Except

Sie können auch spezifizieren, welche Routen Sie erstellen möchten, indem Sie die Optionen only und except verwenden.

// Whitelist only these methods and blacklist the rest
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// Blacklist only these methods and whitelist the rest
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);

Dies sind im Wesentlichen Whitelist- und Blacklist-Optionen, damit Sie spezifizieren können, welche Routen Sie erstellen möchten.

Middleware

Sie können auch Middleware spezifizieren, die auf jeder der von der resource-Methode erstellten Routen ausgeführt wird.

Flight::resource('/users', UsersController::class, [ 'middleware' => [ MyAuthMiddleware::class ] ]);

Streaming-Antworten

Sie können nun Antworten an den Client streamen, indem Sie stream() oder streamWithHeaders() verwenden. Dies ist nützlich für das Senden großer Dateien, lang laufender Prozesse oder das Generieren großer Antworten. Das Streamen einer Route wird etwas anders gehandhabt als eine normale Route.

Hinweis: Streaming-Antworten sind nur verfügbar, wenn Sie flight.v2.output_buffering auf false gesetzt haben.

Stream mit manuellen Headern

Sie können eine Antwort an den Client streamen, indem Sie die stream()-Methode auf einer Route verwenden. Wenn Sie das tun, müssen Sie alle Header manuell setzen, bevor Sie etwas an den Client ausgeben. Dies geschieht mit der header()-PHP-Funktion oder der Flight::response()->setRealHeader()-Methode.

Flight::route('/@filename', function($filename) {

    $response = Flight::response();

    // obviously you would sanitize the path and whatnot.
    $fileNameSafe = basename($filename);

    // If you have additional headers to set here after the route has executed
    // you must define them before anything is echoed out.
    // They must all be a raw call to the header() function or 
    // a call to Flight::response()->setRealHeader()
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    // or
    $response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');

    $filePath = '/some/path/to/files/'.$fileNameSafe;

    if (!is_readable($filePath)) {
        Flight::halt(404, 'File not found');
    }

    // manually set the content length if you'd like
    header('Content-Length: '.filesize($filePath));
    // or
    $response->setRealHeader('Content-Length: '.filesize($filePath));

    // Stream the file to the client as it's read
    readfile($filePath);

// This is the magic line here
})->stream();

Stream mit Headern

Sie können auch die streamWithHeaders()-Methode verwenden, um die Header zu setzen, bevor Sie mit dem Streamen beginnen.

Flight::route('/stream-users', function() {

    // you can add any additional headers you want here
    // you just must use header() or Flight::response()->setRealHeader()

    // however you pull your data, just as an example...
    $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 ',';
        }

        // This is required to send the data to the client
        ob_flush();
    }
    echo '}';

// This is how you'll set the headers before you start streaming.
})->streamWithHeaders([
    'Content-Type' => 'application/json',
    'Content-Disposition' => 'attachment; filename="users.json"',
    // optional status code, defaults to 200
    'status' => 200
]);

Siehe auch

Fehlerbehebung

404 Not Found oder unerwartetes Routenverhalten

Wenn Sie einen 404 Not Found-Fehler sehen (aber Sie schwören bei Ihrem Leben, dass er wirklich da ist und es kein Tippfehler ist), könnte dies tatsächlich ein Problem damit sein, dass Sie einen Wert in Ihrem Routen-Endpunkt zurückgeben, anstatt ihn nur auszugeben. Der Grund dafür ist absichtlich, könnte aber einige Entwickler überraschen.

Flight::route('/hello', function(){
    // This might cause a 404 Not Found error
    return 'Hello World';
});

// What you probably want
Flight::route('/hello', function(){
    echo 'Hello World';
});

Der Grund dafür ist ein spezieller Mechanismus, der in den Router integriert ist und die Rückgabeausgabe als Signal behandelt, um "zur nächsten Route zu gehen". Sie können das Verhalten in dem Abschnitt Routing dokumentiert sehen.

Changelog

Learn/learn

Über Flight lernen

Flight ist ein schnelles, einfaches, erweiterbares Framework für PHP. Es ist sehr vielseitig und kann für die Erstellung aller Arten von Webanwendungen verwendet werden. Es ist mit dem Gedanken an Einfachheit gebaut und auf eine Weise geschrieben, die leicht zu verstehen und zu verwenden ist.

Hinweis: Sie werden Beispiele sehen, die Flight:: als statische Variable verwenden, und einige, die das Engine-Objekt $app-> verwenden. Beide funktionieren austauschbar mit dem anderen. $app und $this->app in einem Controller/Middleware ist der empfohlene Ansatz vom Flight-Team.

Kernkomponenten

Routing

Lernen Sie, wie Sie Routen für Ihre Webanwendung verwalten. Dies umfasst auch das Gruppieren von Routen, Routenparameter und Middleware.

Middleware

Lernen Sie, wie Sie Middleware verwenden, um Anfragen und Antworten in Ihrer Anwendung zu filtern.

Autoloading

Lernen Sie, wie Sie Ihre eigenen Klassen in Ihrer Anwendung autoloaden.

Requests

Lernen Sie, wie Sie Anfragen und Antworten in Ihrer Anwendung handhaben.

Responses

Lernen Sie, wie Sie Antworten an Ihre Benutzer senden.

HTML Templates

Lernen Sie, wie Sie den integrierten View-Engine verwenden, um Ihre HTML-Templates zu rendern.

Security

Lernen Sie, wie Sie Ihre Anwendung vor gängigen Sicherheitsbedrohungen schützen.

Configuration

Lernen Sie, wie Sie das Framework für Ihre Anwendung konfigurieren.

Event Manager

Lernen Sie, wie Sie das Event-System verwenden, um benutzerdefinierte Events zu Ihrer Anwendung hinzuzufügen.

Extending Flight

Lernen Sie, wie Sie das Framework erweitern, indem Sie eigene Methoden und Klassen hinzufügen.

Method Hooks and Filtering

Lernen Sie, wie Sie Event-Hooks zu Ihren Methoden und internen Framework-Methoden hinzufügen.

Dependency Injection Container (DIC)

Lernen Sie, wie Sie Dependency-Injection-Container (DIC) verwenden, um die Abhängigkeiten Ihrer Anwendung zu verwalten.

Utility-Klassen

Collections

Collections werden verwendet, um Daten zu speichern und sie als Array oder als Objekt zugänglich zu machen, um die Bedienung zu erleichtern.

JSON Wrapper

Dies bietet einige einfache Funktionen, um das Encodieren und Decodieren von JSON konsistent zu gestalten.

PDO Wrapper

PDO kann manchmal mehr Kopfschmerzen verursachen als notwendig. Diese einfache Wrapper-Klasse kann die Interaktion mit Ihrer Datenbank erheblich erleichtern.

Uploaded File Handler

Eine einfache Klasse, die hilft, hochgeladene Dateien zu verwalten und sie an einen permanenten Speicherort zu verschieben.

Wichtige Konzepte

Warum ein Framework?

Hier ist ein kurzer Artikel darüber, warum Sie ein Framework verwenden sollten. Es ist eine gute Idee, die Vorteile der Verwendung eines Frameworks zu verstehen, bevor Sie eines einsetzen.

Zusätzlich wurde ein exzellentes Tutorial von @lubiana erstellt. Obwohl es nicht insbesondere auf Flight eingeht, hilft diese Anleitung Ihnen, einige der wichtigsten Konzepte rund um ein Framework zu verstehen und warum sie nützlich sind. Sie finden das Tutorial hier.

Flight im Vergleich zu anderen Frameworks

Wenn Sie von einem anderen Framework wie Laravel, Slim, Fat-Free oder Symfony zu Flight migrieren, hilft diese Seite Ihnen, die Unterschiede zwischen den beiden zu verstehen.

Andere Themen

Unit Testing

Folgen Sie dieser Anleitung, um zu lernen, wie Sie Ihren Flight-Code mit Unit-Tests robust machen.

AI & Developer Experience

Lernen Sie, wie Flight mit AI-Tools und modernen Developer-Workflows zusammenarbeitet, um Ihnen zu helfen, schneller und smarter zu coden.

Migrating v2 -> v3

Rückwärtskompatibilität wurde größtenteils beibehalten, aber es gibt einige Änderungen, die Sie bei der Migration von v2 zu v3 beachten sollten.

Learn/unit_testing

Unit Testing

Übersicht

Unit Testing in Flight hilft Ihnen, sicherzustellen, dass Ihre Anwendung wie erwartet funktioniert, Fehler frühzeitig zu erkennen und Ihren Codebase leichter wartbar zu machen. Flight ist so konzipiert, dass es reibungslos mit PHPUnit zusammenarbeitet, dem beliebtesten PHP-Testing-Framework.

Verständnis

Unit-Tests überprüfen das Verhalten kleiner Teile Ihrer Anwendung (wie Controller oder Services) isoliert. In Flight bedeutet das, zu testen, wie Ihre Routen, Controller und Logik auf verschiedene Eingaben reagieren – ohne auf globalen Zustand oder echte externe Services angewiesen zu sein.

Wichtige Prinzipien:

Grundlegende Verwendung

PHPUnit einrichten

  1. Installieren Sie PHPUnit mit Composer:
    composer require --dev phpunit/phpunit
  2. Erstellen Sie ein tests-Verzeichnis im Root-Verzeichnis 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>

Nun können Sie Ihre Tests mit composer test ausführen.

Testen eines einfachen Route-Handlers

Nehmen Sie an, 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:

Dependency Injection für testbare Controller verwenden

Injizieren Sie Abhängigkeiten (wie die Datenbank oder den Mailer) in Ihre Controller, um sie in Tests leicht zu mocken:

use flight\database\PdoWrapper;

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

Changelog

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

Überblick

Die PdoWrapper-Klasse in Flight ist eine benutzerfreundliche Hilfsklasse für die Arbeit mit Datenbanken unter Verwendung von PDO. Sie vereinfacht gängige Datenbankaufgaben, fügt 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 außerdem Protokollierung von Abfragen und Überwachung der Anwendungsleistung (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 das Abfragen, Abrufen und Behandeln von Ergebnissen erheblich erleichtern. Statt mit vorbereiteten Anweisungen und Abrufmodi zu jonglieren, erhalten Sie einfache Methoden für gängige Aufgaben, und jede Zeile wird als Collection zurückgegeben, sodass Sie Array- oder Objektnotation verwenden können.

Sie können PdoWrapper als geteilten Dienst in Flight registrieren und es dann überall in Ihrer App über Flight::db() verwenden.

Grundlegende Verwendung

Registrieren der PDO-Hilfsklasse

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

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;

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;
}

Verwendung von IN()-Platzhaltern

Sie können einen einzelnen ?-Platzhalter in einer IN()-Klausel verwenden und ein Array oder einen komma-getrennten 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

Abfrageprotokollierung & APM

Wenn Sie die Abfrageleistung verfolgen möchten, aktivieren Sie die APM-Überwachung 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 protokollieren, aber das APM protokolliert sie automatisch, wenn es aktiviert ist:

Flight::db()->logQueries();

Dies löst ein Ereignis (flight.db.queries) mit Verbindungs- und Abfragemetriken aus, das Sie mit dem Ereignissystem von Flight 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

Overview

Der Dependency Injection Container (DIC) ist eine leistungsstarke Erweiterung, die es Ihnen ermöglicht, die Abhängigkeiten Ihrer Anwendung zu verwalten.

Understanding

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 elegante Möglichkeit, Ihre Klassen an einem zentralen Ort zu erstellen und zu verwalten. Dies ist nützlich, wenn Sie dasselbe Objekt an mehrere Klassen weitergeben müssen (z. B. an Ihre Controller oder Middleware).

Basic Usage

Die alte Methode könnte so aussehen:


require 'vendor/autoload.php';

// class to manage users from the database
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 your routes.php file

$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');

$UserController = new UserController($db);
Flight::route('/user/@id', [ $UserController, 'view' ]);
// other UserController routes...

Flight::start();

Aus dem obigen Code können Sie sehen, dass wir ein neues PDO-Objekt erstellen und es an unsere UserController-Klasse weitergeben. 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 weitergeben. Hier kommt ein DIC ins Spiel.

Hier ist dasselbe Beispiel mit einem DIC (unter Verwendung von Dice):


require 'vendor/autoload.php';

// same class as above. Nothing changed
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());
    }
}

// create a new container
$container = new \Dice\Dice;

// add a rule to tell the container how to create a PDO object
// don't forget to reassign it to itself like below!
$container = $container->addRule('PDO', [
    // shared means that the same object will be returned each time
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// This registers the container handler so Flight knows to use it.
Flight::registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// now we can use the container to create our UserController
Flight::route('/user/@id', [ UserController::class, 'view' ]);

Flight::start();

Ich wette, Sie denken, dass eine Menge zusätzlicher Code zum Beispiel hinzugefügt wurde. Die Magie entsteht, wenn Sie einen anderen Controller haben, der das PDO-Objekt benötigt.


// If all your controllers have a constructor that needs a PDO object
// each of the routes below will automatically have it injected!!!
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' ]);

Der zusätzliche Vorteil der Nutzung eines DIC ist, dass Unit-Testing viel einfacher wird. Sie können ein Mock-Objekt erstellen und es an Ihre Klasse weitergeben. Das ist ein großer Vorteil, wenn Sie Tests für Ihre Anwendung schreiben!

Creating a centralized DIC handler

Sie können einen zentralen DIC-Handler in Ihrer Services-Datei erstellen, indem Sie Ihre App erweitern. Hier ist ein Beispiel:

// services.php

// create a new container
$container = new \Dice\Dice;
// don't forget to reassign it to itself like below!
$container = $container->addRule('PDO', [
    // shared means that the same object will be returned each time
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// now we can create a mappable method to create any object. 
Flight::map('make', function($class, $params = []) use ($container) {
    return $container->create($class, $params);
});

// This registers the container handler so Flight knows to use it for controllers/middleware
Flight::registerContainerHandler(function($class, $params) {
    Flight::make($class, $params);
});

// lets say we have the following sample class that takes a PDO object in the constructor
class EmailCron {
    protected PDO $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    public function send() {
        // code that sends an email
    }
}

// And finally you can create objects using dependency injection
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();

flightphp/container

Flight hat ein Plugin, das einen einfachen PSR-11-konformen Container bereitstellt, den Sie zur Handhabung Ihrer Dependency Injection verwenden können. Hier ist ein schnelles Beispiel, wie Sie es verwenden:


// index.php for example
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);
    // will output this correctly!
  }
}

Flight::route('GET /', [TestController::class, 'index']);

Flight::start();

Advanced Usage of flightphp/container

Sie können auch Abhängigkeiten 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 {
    // Implementation ...
    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 ist (Dice). Siehe den Abschnitt basic usage für die Vorgehensweise.

Zusätzlich gibt es einige hilfreiche Standardeinstellungen, die Ihr Leben mit Flight erleichtern.

Engine Instance

Wenn Sie die Engine-Instanz in Ihren Controllern/Middleware verwenden, hier ist, wie Sie sie konfigurieren würden:


// Somewhere in your bootstrap file
$engine = Flight::app();

$container = new \Dice\Dice;
$container = $container->addRule('*', [
    'substitutions' => [
        // This is where you pass in the instance
        Engine::class => $engine
    ]
]);

$engine->registerContainerHandler(function($class, $params) use ($container) {
    return $container->create($class, $params);
});

// Now you can use the Engine instance in your controllers/middleware

class MyController {
    public function __construct(Engine $app) {
        $this->app = $app;
    }

    public function index() {
        $this->app->render('index');
    }
}

Adding Other Classes

Wenn Sie andere 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;
// If you don't need to inject any dependencies into your classes
// you don't need to define anything!
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 die PSR-11-Schnittstelle implementiert. Hier ist ein Beispiel mit Leagues PSR-11-Container:


require 'vendor/autoload.php';

// same UserController class as above

$container = new \League\Container\Container();
$container->add(UserController::class)->addArgument(PdoWrapper::class);
$container->add(PdoWrapper::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, es erledigt dennoch die Aufgabe mit denselben Vorteilen!

See Also

Troubleshooting

Changelog

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 einige grundlegende Funktionen für HTML-Templating. Templating ist eine sehr effektive Methode, um die Anwendungslogik von der Präsentationsschicht zu trennen.

Verständnis

Wenn Sie eine Anwendung erstellen, haben Sie wahrscheinlich HTML, das Sie an den Endbenutzer zurückliefern möchten. PHP ist an sich eine Templating-Sprache, aber es ist sehr einfach, Geschäftslogik wie Datenbankaufrufe, API-Aufrufe usw. in Ihre HTML-Datei zu integrieren und das Testen und Entkoppeln zu einem sehr schwierigen Prozess zu machen. Indem Sie Daten in eine Vorlage schieben und die Vorlage sich selbst rendern lassen, wird es viel einfacher, Ihren Code zu entkoppeln und Unit-Tests durchzuführen. Sie werden uns dankbar sein, wenn Sie Vorlagen verwenden!

Grundlegende Verwendung

Flight ermöglicht es Ihnen, den Standard-View-Engine einfach zu ersetzen, indem Sie Ihre eigene View-Klasse registrieren. Scrollen Sie nach unten, um Beispiele zu sehen, wie Sie Smarty, Latte, Blade und mehr verwenden können!

Latte

empfohlen

Hier ist, wie Sie den Latte Template-Engine für Ihre Ansichten verwenden würden.

Installation

composer require latte/latte

Grundlegende Konfiguration

Die Hauptidee ist, dass Sie die render-Methode überschreiben, um Latte anstelle des Standard-PHP-Renders zu verwenden.

// überschreiben der render-Methode, um Latte anstelle des Standard-PHP-Renders zu verwenden
Flight::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);
});

Verwendung von Latte in Flight

Jetzt, da Sie mit Latte rendern können, können Sie etwas wie das tun:

<!-- app/views/home.latte -->
<html>
  <head>
    <title>{$title ? $title . ' - '}My App</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <h1>Hello, {$name}!</h1>
  </body>
</html>
// routes.php
Flight::route('/@name', function ($name) {
    Flight::render('home.latte', [
        'title' => 'Home Page',
        'name' => $name
    ]);
});

Wenn Sie /Bob in Ihrem Browser besuchen, wäre die Ausgabe:

<html>
  <head>
    <title>Home Page - My App</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <h1>Hello, Bob!</h1>
  </body>
</html>

Weiterführende Lektüre

Ein komplexeres Beispiel zur Verwendung von Latte mit Layouts wird im Abschnitt awesome plugins dieser Dokumentation gezeigt.

Sie können mehr über die vollen Fähigkeiten von Latte, einschließlich Übersetzung und Sprachfähigkeiten, erfahren, indem Sie die offizielle Dokumentation lesen.

Eingebauter View-Engine

veraltet

Hinweis: Obwohl dies immer noch die Standardfunktionalität ist und technisch noch funktioniert.

Um eine View-Vorlage anzuzeigen, rufen Sie die render-Methode mit dem Namen der Vorlagendatei und optionalen Vorlagendaten auf:

Flight::render('hello.php', ['name' => 'Bob']);

Die Vorlagendaten, die Sie übergeben, werden automatisch in die Vorlage injiziert und können wie eine lokale Variable referenziert werden. Vorlagendateien sind einfach PHP-Dateien. Wenn der Inhalt der hello.php-Vorlagendatei so aussieht:

Hello, <?= $name ?>!

Wäre die Ausgabe:

Hello, Bob!

Sie können View-Variablen auch manuell mit der set-Methode festlegen:

Flight::view()->set('name', 'Bob');

Die Variable name ist jetzt in allen Ihren Views verfügbar. Also können Sie einfach tun:

Flight::render('hello');

Beachten Sie, dass beim Angabe des Namens der Vorlage in der render-Methode die .php-Erweiterung weggelassen werden kann.

Standardmäßig sucht Flight nach einem views-Verzeichnis für Vorlagendateien. Sie können einen alternativen Pfad für Ihre Vorlagen festlegen, indem Sie die folgende Konfiguration setzen:

Flight::set('flight.views.path', '/path/to/views');

Layouts

Es ist üblich, dass Websites eine einzige Layout-Vorlagendatei mit austauschbarem Inhalt haben. Um Inhalt zu rendern, der in einem Layout verwendet werden soll, können Sie einen optionalen Parameter an die render-Methode übergeben.

Flight::render('header', ['heading' => 'Hello'], 'headerContent');
Flight::render('body', ['body' => 'World'], 'bodyContent');

Ihre View wird dann gespeicherte Variablen namens headerContent und bodyContent haben. Sie können dann Ihr Layout rendern, indem Sie tun:

Flight::render('layout', ['title' => 'Home Page']);

Wenn die Vorlagendateien so aussehen:

header.php:

<h1><?= $heading ?></h1>

body.php:

<div><?= $body ?></div>

layout.php:

<html>
  <head>
    <title><?= $title ?></title>
  </head>
  <body>
    <?= $headerContent ?>
    <?= $bodyContent ?>
  </body>
</html>

Wäre die Ausgabe:

<html>
  <head>
    <title>Home Page</title>
  </head>
  <body>
    <h1>Hello</h1>
    <div>World</div>
  </body>
</html>

Smarty

Hier ist, wie Sie den Smarty Template-Engine für Ihre Ansichten verwenden würden:

// Laden der Smarty-Bibliothek
require './Smarty/libs/Smarty.class.php';

// Registrieren von Smarty als View-Klasse
// Auch Übergeben einer 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/');
});

// Zuweisen von Vorlagendaten
Flight::view()->assign('name', 'Bob');

// Anzeigen der Vorlage
Flight::view()->display('hello.tpl');

Zur Vollständigkeit sollten Sie auch die Standard-render-Methode von Flight überschreiben:

Flight::map('render', function(string $template, array $data): void {
  Flight::view()->assign($data);
  Flight::view()->display($template);
});

Blade

Hier ist, wie Sie den Blade Template-Engine für Ihre Ansichten verwenden würden:

Zuerst müssen Sie die BladeOne-Bibliothek über Composer installieren:

composer require eftec/bladeone

Dann können Sie BladeOne als View-Klasse in Flight konfigurieren:

<?php
// Laden der BladeOne-Bibliothek
use eftec\bladeone\BladeOne;

// Registrieren von BladeOne als View-Klasse
// Auch Übergeben einer 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);
});

// Zuweisen von Vorlagendaten
Flight::view()->share('name', 'Bob');

// Anzeigen der Vorlage
echo Flight::view()->run('hello', []);

Zur Vollständigkeit sollten Sie auch die Standard-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 Datei hello.blade.php so aussehen:

<?php
Hello, {{ $name }}!

Die Ausgabe wäre:

Hello, Bob!

Siehe auch

Fehlerbehebung

Changelog

Learn/collections

Collections

Überblick

Die Collection-Klasse in Flight ist ein nützliches Hilfsprogramm zum Verwalten von Datensätzen. Sie ermöglicht den Zugriff und die Manipulation von Daten mit Array- und Objekt-Notation, was Ihren Code sauberer und flexibler macht.

Verständnis

Eine Collection ist im Wesentlichen eine Umhüllung um ein Array, aber mit zusätzlichen Fähigkeiten. Sie können sie wie ein Array verwenden, darüber iterieren, die Anzahl ihrer Elemente zählen und sogar auf Elemente zugreifen, als wären sie Objekteigenschaften. Dies ist besonders nützlich, wenn Sie strukturierte Daten in Ihrer App weitergeben möchten oder Ihren Code lesbarer gestalten wollen.

Collections implementieren mehrere PHP-Schnittstellen:

Grundlegende Verwendung

Erstellen einer Collection

Sie können eine Collection erstellen, indem Sie einfach ein Array an ihren Konstruktor übergeben:

use flight\util\Collection;

$data = [
  'name' => 'Flight',
  'version' => 3,
  'features' => ['routing', 'views', 'extending']
];

$collection = new Collection($data);

Zugriff auf Elemente

Sie können auf Elemente mit Array- oder Objekt-Notation zugreifen:

// Array-Notation
echo $collection['name']; // Ausgabe: FlightPHP

// Objekt-Notation
echo $collection->version; // Ausgabe: 3

Wenn Sie versuchen, auf einen Schlüssel zuzugreifen, der nicht existiert, erhalten Sie null anstelle eines Fehlers.

Setzen von Elementen

Sie können Elemente mit beiden Notationen setzen:

// Array-Notation
$collection['author'] = 'Mike Cao';

// Objekt-Notation
$collection->license = 'MIT';

Überprüfen und Entfernen von Elementen

Überprüfen Sie, ob ein Element existiert:

if (isset($collection['name'])) {
  // Etwas tun
}

if (isset($collection->version)) {
  // Etwas tun
}

Entfernen Sie ein Element:

unset($collection['author']);
unset($collection->license);

Iterieren über eine Collection

Collections sind iterierbar, sodass Sie sie in einer foreach-Schleife verwenden können:

foreach ($collection as $key => $value) {
  echo "$key: $value\n";
}

Zählen von Elementen

Sie können 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 umgewandelt werden:

echo json_encode($collection);
// Ausgabe: {"name":"FlightPHP","version":3,"features":["routing","views","extending"],"license":"MIT"}

Erweiterte Verwendung

Sie können das interne Daten-Array vollständig ersetzen, falls benötigt:

$collection->setData(['foo' => 'bar']);

Collections sind besonders nützlich, wenn Sie strukturierte Daten zwischen Komponenten weitergeben möchten oder eine objektorientiertere Schnittstelle für Array-Daten bereitstellen wollen.

Siehe auch

Fehlerbehebung

Änderungsprotokoll

Learn/flight_vs_fat_free

Flight vs Fat-Free

Was ist Fat-Free?

Fat-Free (liebenswürdig bekannt als F3) ist ein leistungsstarkes, aber einfach zu bedienendes PHP-Micro-Framework, das Ihnen hilft, dynamische und robuste Web-Anwendungen – schnell – zu erstellen!

Flight vergleicht sich mit Fat-Free in vielerlei Hinsicht und ist wahrscheinlich der nächste Verwandte in Bezug auf Funktionen und Einfachheit. Fat-Free hat eine Menge Funktionen, die Flight nicht hat, aber es hat auch viele Funktionen, die Flight hat. Fat-Free zeigt langsam sein Alter 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 es schwierig machen, ihn zu lesen und zu verstehen. Es funktioniert für PHP 8.3, aber der Code selbst sieht immer noch so aus, als würde er in PHP 5.3 leben.

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 Ihnen hilft, schnell einfache, aber leistungsstarke Webanwendungen und APIs zu schreiben.

Viel 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 spezifischen Reihenfolge sind zwei Funktionen, die von Slim inspiriert wurden. Slim v3 wurde mit dem 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

Überblick

Autoloading ist ein Konzept in PHP, bei dem Sie ein Verzeichnis oder Verzeichnisse angeben, aus denen Klassen geladen werden. Dies ist viel vorteilhafter als die Verwendung von require oder include, um Klassen zu laden. Es ist auch eine Voraussetzung für die Verwendung von Composer-Paketen.

Verständnis

Standardmäßig wird jede Flight-Klasse automatisch für Sie autogeladen dank Composer. Wenn Sie jedoch Ihre eigenen Klassen autoladen möchten, können Sie die Methode Flight::path() verwenden, um ein Verzeichnis anzugeben, aus dem Klassen geladen werden.

Die Verwendung eines Autoloaders kann Ihren Code auf erhebliche Weise vereinfachen. Anstatt dass Dateien mit einer Vielzahl von include- oder require-Anweisungen am Anfang beginnen, um alle in dieser Datei verwendeten Klassen zu erfassen, können Sie stattdessen Ihre Klassen dynamisch aufrufen, und sie werden automatisch eingeschlossen.

Grundlegende Verwendung

Nehmen wir an, wir haben eine Verzeichnisstruktur wie die folgende:

# 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

Sie haben vielleicht bemerkt, dass dies die gleiche Dateistruktur wie diese Dokumentationsseite ist.

Sie können jedes Verzeichnis zum Laden wie folgt angeben:


/**
 * public/index.php
 */

// Einen Pfad zum Autoloader hinzufügen
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');

/**
 * app/controllers/MyController.php
 */

// Kein Namespacing erforderlich

// Alle autogeladenen Klassen sollten im Pascal Case sein (jedes Wort großgeschrieben, keine Leerzeichen)
class MyController {

    public function index() {
        // etwas tun
    }
}

Namespaces

Wenn Sie Namespaces haben, wird es tatsächlich sehr einfach, dies zu implementieren. Sie sollten die Methode Flight::path() verwenden, um das Stammverzeichnis (nicht das Dokumentenroot oder das public/-Verzeichnis) Ihrer Anwendung anzugeben.


/**
 * public/index.php
 */

// Einen Pfad zum Autoloader hinzufügen
Flight::path(__DIR__.'/../');

Nun könnte Ihr Controller so aussehen. Schauen Sie sich das Beispiel unten an, aber achten Sie auf die Kommentare für wichtige Informationen.

/**
 * app/controllers/MyController.php
 */

// Namespaces sind erforderlich
// Namespaces sind identisch mit der Verzeichnisstruktur
// Namespaces müssen die gleiche Schreibweise wie die Verzeichnisstruktur haben
// Namespaces und Verzeichnisse dürfen keine Unterstriche enthalten (es sei denn, Loader::setV2ClassLoading(false) ist gesetzt)
namespace app\controllers;

// Alle autogeladenen Klassen sollten im 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 autoladen möchten, würden Sie im Wesentlichen dasselbe tun:


/**
 * app/UTILS/ArrayHelperUtil.php
 */

// Namespace muss der Verzeichnisstruktur und Schreibweise entsprechen (beachten Sie, dass das UTILS-Verzeichnis in Großbuchstaben ist
//     wie im Dateibaum oben)
namespace app\UTILS;

class ArrayHelperUtil {

    public function changeArrayCase(array $array) {
        // etwas tun
    }
}

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. Dies ermöglicht die Verwendung von Unterstrichen in Ihren Klassennamen. Dies wird nicht empfohlen, ist aber für diejenigen verfügbar, die es benötigen.

use flight\core\Loader;

/**
 * public/index.php
 */

// Einen Pfad zum Autoloader hinzufügen
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);

/**
 * app/controllers/My_Controller.php
 */

// Kein Namespacing erforderlich

class My_Controller {

    public function index() {
        // etwas tun
    }
}

Siehe auch

Fehlerbehebung

Klasse nicht gefunden (Autoloading funktioniert nicht)

Es könnte ein paar Gründe dafür geben, dass dies nicht passiert. Unten sind einige Beispiele, aber stellen Sie sicher, dass Sie auch den Abschnitt autoloading überprüfen.

Falscher Dateiname

Der häufigste Grund ist, dass der Klassenname nicht zum Dateinamen passt.

Wenn Sie eine Klasse namens MyClass haben, sollte die Datei MyClass.php heißen. Wenn Sie eine Klasse namens MyClass haben und die Datei myclass.php heißt, kann der Autoloader sie nicht finden.

Falscher Namespace

Wenn Sie Namespaces verwenden, sollte der Namespace der Verzeichnisstruktur entsprechen.

// ...code...

// wenn Ihr MyController im app/controllers-Verzeichnis ist und namespaced
// das wird nicht funktionieren.
Flight::route('/hello', 'MyController->hello');

// Sie müssen eine dieser Optionen wählen
Flight::route('/hello', 'app\controllers\MyController->hello');
// oder wenn Sie oben eine use-Anweisung haben

use app\controllers\MyController;

Flight::route('/hello', [ MyController::class, 'hello' ]);
// kann auch so geschrieben werden
Flight::route('/hello', MyController::class.'->hello');
// auch...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);

path() nicht definiert

In der Skeleton-App wird dies in der config.php-Datei definiert, aber damit Ihre Klassen gefunden werden, müssen Sie sicherstellen, dass die path()-Methode definiert ist (wahrscheinlich zum Stammverzeichnis Ihres Verzeichnisses), bevor Sie sie verwenden.

// Einen Pfad zum Autoloader hinzufügen
Flight::path(__DIR__.'/../');

Ä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 Testing in Flight PHP mit PHPUnit

Dieser Leitfaden führt Unit Testing in Flight PHP mit PHPUnit ein, gerichtet an Anfänger, die verstehen möchten, warum Unit Testing wichtig ist und wie man es praktisch anwendet. Wir konzentrieren uns auf das Testen von Verhalten – das Sicherstellen, dass Ihre Anwendung das tut, was Sie erwarten, wie das Senden einer E-Mail oder das Speichern eines Datensatzes – anstelle von trivialen Berechnungen. Wir beginnen mit einem einfachen Route Handler und gehen zu einem komplexeren Controller über, unter Einbeziehung von Dependency Injection (DI) und dem Mocken von Drittanbieter-Services.

Warum Unit Tests?

Unit Testing stellt sicher, dass Ihr Code wie erwartet verhält, und fängt Bugs ab, bevor sie in die Produktion gelangen. Es ist besonders wertvoll in Flight, wo leichte Routing und Flexibilität zu komplexen Interaktionen führen können. Für Solo-Entwickler oder Teams dienen Unit Tests als Sicherheitsnetz, dokumentieren erwartetes Verhalten und verhindern Regressionen, wenn Sie später Code erneut betrachten. Sie verbessern auch das Design: Schwierig zu testender Code signalisiert oft übermäßig komplexe oder eng gekoppelte Klassen.

Im Gegensatz zu simplistischen Beispielen (z. B. Testen von x * y = z) konzentrieren wir uns auf reale Verhaltensweisen, wie die Validierung von Eingaben, das Speichern von Daten oder das Auslösen von Aktionen wie E-Mails. Unser Ziel ist es, Testing zugänglich und sinnvoll zu machen.

Allgemeine Leitprinzipien

  1. Verhalten testen, nicht Implementierung: Konzentrieren Sie sich auf Ergebnisse (z. B. „E-Mail gesendet“ oder „Datensatz gespeichert“) anstelle interner Details. Das macht Tests robust gegenüber Refactoring.
  2. Vermeiden Sie Flight::: Flights statische Methoden sind bequem, machen Testing aber schwierig. Gewöhnen Sie sich daran, die $app-Variable aus $app = Flight::app(); zu verwenden. $app hat alle Methoden, die Flight:: hat. Sie können immer noch $app->route() oder $this->app->json() in Ihrem Controller usw. verwenden. Verwenden Sie auch den echten Flight-Router mit $router = $app->router() und dann können Sie $router->get(), $router->post(), $router->group() usw. nutzen. Siehe Routing.
  3. Halten Sie Tests schnell: Schnelle Tests fördern häufige Ausführung. Vermeiden Sie langsame Operationen wie Datenbankaufrufe in Unit Tests. Wenn Sie einen langsamen Test haben, ist das ein Zeichen, dass Sie einen Integration Test schreiben, keinen Unit Test. Integration Tests beinhalten echte Datenbanken, echte HTTP-Aufrufe, echtes E-Mail-Versenden usw. Sie haben ihren Platz, sind aber langsam und können unzuverlässig sein, was bedeutet, dass sie manchmal aus unbekannten Gründen fehlschlagen.
  4. Verwenden Sie beschreibende Namen: Testnamen sollten das getestete Verhalten klar beschreiben. Das verbessert Lesbarkeit und Wartbarkeit.
  5. Vermeiden Sie Globals wie die Pest: Minimieren Sie die Nutzung 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 Dependency Injection Container). Sogar die Verwendung der Methode $app->map() ist technisch „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 das eine globale Variable in Ihren Code injiziert und das Testing erschwert.
  6. Verwenden Sie Dependency Injection: Injizieren Sie Abhängigkeiten (z. B. PDO, Mailer) in Controller, um Logik zu isolieren und das Mocken zu vereinfachen. Wenn eine Klasse zu viele Abhängigkeiten hat, überlegen Sie, sie in kleinere Klassen umzustrukturieren, die jeweils eine einzige Verantwortung haben und den SOLID-Prinzipien folgen.
  7. Mocken Sie Drittanbieter-Services: Mocken Sie Datenbanken, HTTP-Clients (cURL) oder E-Mail-Services, um externe Aufrufe zu vermeiden. Testen Sie eine oder zwei Ebenen tief, lassen Sie aber Ihre Kernlogik laufen. Zum Beispiel, wenn Ihre App eine SMS sendet, wollen Sie NICHT wirklich eine SMS bei jedem Testlauf senden, da die Kosten steigen (und es langsamer wird). Stattdessen mocken Sie den SMS-Service und überprüfen nur, ob Ihr Code den SMS-Service mit den richtigen Parametern aufgerufen hat.
  8. Streben Sie hohe Abdeckung an, nicht Perfektion: 100% Zeilenabdeckung ist gut, bedeutet aber nicht, dass alles in Ihrem Code so getestet wird, wie es sein sollte (recherchieren Sie Branch/Path Coverage in PHPUnit). Priorisieren Sie kritische Verhaltensweisen (z. B. Benutzerregistrierung, API-Antworten und das Erfassen fehlgeschlagener Antworten).
  9. Verwenden Sie Controller für Routes: In Ihren Route-Definitionen verwenden Sie Controller, keine Closures. Die flight\Engine $app wird standardmäßig über den Konstruktor in jeden Controller injiziert. In Tests verwenden Sie $app = new Flight\Engine(), um Flight innerhalb eines Tests zu instanziieren, injizieren Sie es in Ihren Controller und rufen Methoden direkt auf (z. B. $controller->register()). Siehe Extending Flight und Routing.
  10. Wählen Sie einen Mocking-Stil und halten Sie sich daran: PHPUnit unterstützt mehrere Mocking-Stile (z. B. prophecy, eingebaute Mocks), oder Sie können anonyme Klassen verwenden, die eigene Vorteile haben wie Code-Vervollständigung, Brechen, wenn Sie die Methodendefinition ändern usw. Seien Sie einfach konsistent in Ihren Tests. Siehe PHPUnit Mock Objects.
  11. Verwenden Sie protected Sichtbarkeit für Methoden/Eigenschaften, die Sie in Subklassen testen möchten: Das erlaubt es, sie in Test-Subklassen zu überschreiben, ohne sie public zu machen, was besonders nützlich für anonyme Klassen-Mocks ist.

Einrichten von PHPUnit

Richten Sie zuerst PHPUnit in Ihrem Flight PHP-Projekt mit Composer ein, für einfaches Testing. Siehe den PHPUnit Getting Started Guide für mehr Details.

  1. In Ihrem Projektverzeichnis ausführen:

    composer require --dev phpunit/phpunit

    Das installiert die neueste PHPUnit als Entwicklungsabhängigkeit.

  2. Erstellen Sie ein tests-Verzeichnis in der Wurzel Ihres Projekts für Testdateien.

  3. Fügen Sie ein Test-Skript zu composer.json für Bequemlichkeit hinzu:

    // andere composer.json-Inhalte
    "scripts": {
       "test": "phpunit --configuration phpunit.xml"
    }
  4. Erstellen Sie eine phpunit.xml-Datei in der Wurzel:

    <?xml version="1.0" encoding="UTF-8"?>
    <phpunit bootstrap="vendor/autoload.php">
       <testsuites>
           <testsuite name="Flight Tests">
               <directory>tests</directory>
           </testsuite>
       </testsuites>
    </phpunit>

Nun können Sie, wenn Ihre Tests aufgebaut sind, composer test ausführen, um Tests auszuführen.

Testen eines einfachen Route Handlers

Lassen Sie uns mit einer grundlegenden Route beginnen, die die E-Mail-Eingabe eines Benutzers validiert. Wir testen ihr Verhalten: Rückgabe einer Erfolgsnachricht für gültige E-Mails und einer Fehlermeldung für ungültige. 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' => 'Ungültige E-Mail'];
        } else {
            $responseArray = ['status' => 'success', 'message' => 'Gültige E-Mail'];
        }

        $this->app->json($responseArray);
    }
}

Um das zu testen, erstellen Sie eine Testdatei. Siehe Unit Testing and SOLID Principles für mehr über die Strukturierung von Tests:

// tests/UserControllerTest.php
use PHPUnit\Framework\TestCase;
use Flight;
use flight\Engine;

// Kommentar: Testklasse für UserController
class UserControllerTest extends TestCase {

    public function testValidEmailReturnsSuccess() {
        $app = new Engine();
        $request = $app->request();
        $request->data->email = 'test@example.com'; // Simulate POST data
        $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('Gültige E-Mail', $output['message']);
    }

    public function testInvalidEmailReturnsError() {
        $app = new Engine();
        $request = $app->request();
        $request->data->email = 'invalid-email'; // Simulate POST data
        $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('Ungültige E-Mail', $output['message']);
    }
}

Wichtige Punkte:

Führen Sie composer test aus, um zu überprüfen, ob die Route wie erwartet verhält. Für mehr über Requests und Responses in Flight siehe die relevanten Docs.

Verwendung von Dependency Injection für testbare Controller

Für komplexere Szenarien verwenden Sie Dependency Injection (DI), um Controller testbar zu machen. Vermeiden Sie Flights Globals (z. B. Flight::set(), Flight::map(), Flight::register()), da sie wie globaler Zustand wirken und Mocks für jeden Test erfordern. Stattdessen verwenden Sie Flights DI-Container, DICE, PHP-DI oder manuelles DI.

Lassen Sie uns flight\database\PdoWrapper anstelle von raw PDO verwenden. Dieser Wrapper ist viel einfacher zu mocken und für Unit Tests!

Hier ist ein Controller, der einen Benutzer in eine Datenbank speichert und eine Willkommens-E-Mail sendet:

use flight\database\PdoWrapper;

// Kommentar: Controller mit DI für Datenbank und Mailer
class UserController {
    protected $app;
    protected $db;
    protected $mailer;

    public function __construct(Engine $app, PdoWrapper $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)) {
            // adding the return here helps unit testing to stop execution
            return $this->app->jsonHalt(['status' => 'error', 'message' => 'Ungültige E-Mail']);
        }

        $this->db->runQuery('INSERT INTO users (email) VALUES (?)', [$email]);
        $this->mailer->sendWelcome($email);

        return $this->app->json(['status' => 'success', 'message' => 'Benutzer registriert']);
    }
}

Wichtige Punkte:

Testen des Controllers mit Mocks

Nun testen wir das Verhalten des UserController: Validierung 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 PHPUnit\Framework\TestCase;

// Kommentar: Testklasse mit DI und Mocks
class UserControllerDICTest extends TestCase {
    public function testValidEmailSavesAndSendsEmail() {

        // Sometimes mixing mocking styles is necessary
        // Here we use PHPUnit's built-in mock for PDOStatement
        $statementMock = $this->createMock(PDOStatement::class);
        $statementMock->method('execute')->willReturn(true);
        // Using an anonymous class to mock PdoWrapper
        $mockDb = new class($statementMock) extends PdoWrapper {
            protected $statementMock;
            public function __construct($statementMock) {
                $this->statementMock = $statementMock;
            }

            // When we mock it this way, we are not really making a database call.
            // We can further setup this to alter the PDOStatement mock to simulate failures, etc.
            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('Benutzer registriert', $result['message']);
        $this->assertEquals('test@example.com', $mockMailer->sentEmail);
    }

    public function testInvalidEmailSkipsSaveAndEmail() {
         $mockDb = new class() extends PdoWrapper {
            // An empty constructor bypasses the parent constructor
            public function __construct() {}
            public function runQuery(string $sql, array $params = []): PDOStatement {
                throw new Exception('Sollte nicht aufgerufen werden');
            }
        };
        $mockMailer = new class implements MailerInterface {
            public $sentEmail = null;
            public function sendWelcome($email): bool {
                throw new Exception('Sollte nicht aufgerufen werden');
            }
        };
        $app = new Engine();
        $app->request()->data->email = 'invalid-email';

        // Need to map jsonHalt to avoid exiting
        $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('Ungültige E-Mail', $result['message']);
    }
}

Wichtige Punkte:

Zu viel Mocken

Seien Sie vorsichtig, nicht zu viel von Ihrem Code zu mocken. Lassen Sie mich ein Beispiel unten geben, warum das schlecht sein könnte, mit unserem UserController. Wir ändern diese Überprüfung in eine Methode namens isEmailValid (mit filter_var) und die anderen neuen Ergänzungen in eine separate Methode namens registerUser.

use flight\database\PdoWrapper;
use flight\Engine;

// UserControllerDICV2.php
class UserControllerDICV2 {
    protected $app;
    protected $db;
    protected $mailer;

    public function __construct(Engine $app, PdoWrapper $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)) {
            // adding the return here helps unit testing to stop execution
            return $this->app->jsonHalt(['status' => 'error', 'message' => 'Ungültige E-Mail']);
        }

        $this->registerUser($email);

        $this->app->json(['status' => 'success', 'message' => 'Benutzer registriert']);
    }

    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;

// Kommentar: Übermäßig gemockter Test, der nichts überprüft
class UserControllerTest extends TestCase {
    public function testValidEmailSavesAndSendsEmail() {
        $app = new Engine();
        $app->request()->data->email = 'test@example.com';
        // we are skipping the extra dependency injection here cause it's "easy"
        $controller = new class($app) extends UserControllerDICV2 {
            protected $app;
            // Bypass the deps in the construct
            public function __construct($app) {
                $this->app = $app;
            }

            // We'll just force this to be valid.
            protected function isEmailValid($email) {
                return true; // Always return true, bypassing real validation
            }

            // Bypass the actual DB and mailer calls
            protected function registerUser($email) {
                return false;
            }
        };
        $controller->register();
        $response = $app->response()->getBody();
        $result = json_decode($response, true);
        $this->assertEquals('success', $result['status']);
        $this->assertEquals('Benutzer registriert', $result['message']);
    }
}

Hurra, wir haben Unit Tests und sie laufen! Aber warte, was, wenn ich tatsächlich die internen Funktionen von isEmailValid oder registerUser ändere? Meine Tests laufen immer noch, weil ich alle Funktionalität gemockt habe. Lassen Sie mich zeigen, was ich meine.

// UserControllerDICV2.php
class UserControllerDICV2 {

    // ... other methods ...

    protected function isEmailValid($email) {
        // Changed logic
        $validEmail = filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
        // Now it should only have a specific domain
        $validDomain = strpos($email, '@example.com') !== false; 
        return $validEmail && $validDomain;
    }
}

Wenn ich meine obigen Unit Tests ausführe, laufen sie immer noch! Aber weil ich nicht für Verhalten getestet habe (tatsächlich etwas Code laufen lassen), habe ich potenziell einen Bug kodiert, der in der Produktion auftritt. Der Test sollte für das neue Verhalten angepasst werden und auch für den umgekehrten Fall, wenn das Verhalten nicht das ist, was wir erwarten.

Vollständiges Beispiel

Sie finden ein vollständiges Beispiel eines Flight PHP-Projekts mit Unit Tests auf GitHub: n0nag0n/flight-unit-tests-guide. Für tieferes Verständnis siehe Unit Testing and SOLID Principles.

Häufige Fallstricke

Skalieren mit Unit Tests

Unit Tests glänzen in größeren Projekten oder beim Wiederbesuch von Code nach Monaten. Sie dokumentieren Verhalten und fangen Regressionen ab, sparen Ihnen das erneute Lernen Ihrer App. Für Solo-Entwickler testen Sie kritische Pfade (z. B. Benutzeranmeldung, Zahlungsabwicklung). Für Teams stellen Tests konsistentes Verhalten über Beiträge hinweg sicher. Siehe Why Frameworks? für mehr über die Vorteile von Frameworks und Tests.

Beitragen Sie Ihre eigenen Testing-Tipps zum Flight PHP-Dokumentations-Repository!

Geschrieben von n0nag0n 2025

Guides/blog

Erstellen eines einfachen Blogs mit Flight PHP

Diese Anleitung führt Sie durch die Erstellung eines einfachen Blogs 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 funktionalen Blog mit einer Startseite, individuellen Beitragsseiten und einem Erstellungsformular.

Voraussetzungen

Schritt 1: Richten Sie Ihr Projekt ein

Beginnen Sie damit, ein neues Projektverzeichnis zu erstellen und Flight über Composer zu installieren.

  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 'Hallo, Flight!';
    });
    
    Flight::start();
  5. Den integrierten Server ausführen: Testen Sie Ihre Einrichtung mit dem Entwicklungsserver von PHP:

    php -S localhost:8000 -t public/

    Besuchen Sie http://localhost:8000, um „Hallo, Flight!“ zu sehen.

Schritt 2: Organisieren Sie Ihre Projektstruktur

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: Installieren und Konfigurieren von Latte

Latte ist eine leichtgewichtige Template-Engine, die gut mit Flight integriert.

  1. Latte installieren:

    composer require latte/latte
  2. Latte in Flight konfigurieren: Aktualisieren Sie public/index.php, um Latte als Ansicht-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' => 'Mein Blog']);
    });
    
    Flight::start();
  3. Eine Layout-Vorlage erstellen: In app/views/layout.latte:

    <!DOCTYPE html>
    <html>
    <head>
    <title>{$title}</title>
    </head>
    <body>
    <header>
        <h1>Mein Blog</h1>
        <nav>
            <a href="/">Startseite</a> | 
            <a href="/create">Beitrag erstellen</a>
        </nav>
    </header>
    <main>
        {block content}{/block}
    </main>
    <footer>
        <p>&copy; {date('Y')} Flight Blog</p>
    </footer>
    </body>
    </html>
  4. Eine Startvorlage 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, wenn Sie ihn verlassen 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 zur Vereinfachung zu simulieren.

In data/posts.json:

[
    {
        "slug": "first-post",
        "title": "Mein erster Beitrag",
        "content": "Dies ist mein allererster Blogbeitrag mit Flight PHP!"
    }
]

Schritt 4: Routen definieren

Trennen Sie Ihre Routen in eine Konfigurationsdatei für eine bessere Organisation.

  1. Erstellen Sie routes.php: In app/config/routes.php:
    
    <?php
    Flight::route('/', function () {
    Flight::view()->render('home.latte', ['title' => 'Mein Blog']);
    });

Flight::route('/post/@slug', function ($slug) { Flight::view()->render('post.latte', ['title' => 'Beitrag: ' . $slug, 'slug' => $slug]); });

Flight::route('GET /create', function () { Flight::view()->render('create.latte', ['title' => 'Beitrag erstellen']); });


2. **Aktualisieren Sie `index.php`**:
Integrieren Sie die Routen-Datei:
```php
<?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: Blog-Beiträge speichern und abrufen

Fügen Sie die Methoden zum Laden und Speichern von Beiträgen hinzu.

  1. Eine Posts-Methode hinzufügen: In index.php, fügen Sie eine Methode hinzu, um Beiträge zu laden:

    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 Beiträge zu verwenden:

    
    <?php
    Flight::route('/', function () {
    $posts = Flight::posts();
    Flight::view()->render('home.latte', [
        'title' => 'Mein 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' => 'Beitrag erstellen']); });


## Schritt 6: Vorlagen erstellen

Aktualisieren Sie Ihre Vorlagen, um Beiträge anzuzeigen.

1. **Beitragsseite (`app/views/post.latte`)**:
```html
{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 zum Hinzufügen neuer Beiträge.

  1. Erstellungsformular (app/views/create.latte):

    {extends 'layout.latte'}
    
    {block content}
        <h2>{$title}</h2>
        <form method="POST" action="/create">
            <div class="form-group">
                <label for="title">Titel:</label>
                <input type="text" name="title" id="title" required>
            </div>
            <div class="form-group">
                <label for="content">Inhalt:</label>
                <textarea name="content" id="content" required></textarea>
            </div>
            <button type="submit">Beitrag speichern</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.
    • Reichen Sie einen neuen Beitrag ein (z. B. „Zweiter Beitrag“ mit etwas Inhalt).
    • Überprüfen Sie die Startseite, um zu sehen, dass er aufgeführt ist.

Schritt 8: Mit Fehlerbehandlung verbessern

Überschreiben Sie die notFound-Methode für ein besseres 404-Erlebnis.

In index.php:

Flight::map('notFound', function () {
    Flight::view()->render('404.latte', ['title' => 'Seite nicht gefunden']);
});

Erstellen Sie app/views/404.latte:

{extends 'layout.latte'}

{block content}
    <h2>404 - {$title}</h2>
    <p>Entschuldigung, diese Seite existiert nicht!</p>
{/block}

Nächste Schritte

Fazit

Sie haben einen einfachen Blog mit Flight PHP erstellt! Diese Anleitung zeigt die grundlegenden Funktionen wie Routing, das Rendern von Vorlagen mit Latte und die Handhabung von Formularübermittlungen – und das alles, während es leichtgewichtig bleibt. Erkunden Sie die Dokumentation von Flight für weitere fortgeschrittene Funktionen, um Ihren Blog weiter auszubauen!

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, simples, erweiterbares Framework für PHP – entwickelt für Entwickler, die Dinge schnell erledigen wollen, ohne Aufwand. Ob Sie eine klassische Web-App, eine blitzschnelle API oder mit den neuesten KI-gestützten Tools experimentieren, Flights geringer Fußabdruck und geradliniges Design machen es zur perfekten Wahl. Flight ist darauf ausgelegt, schlank zu sein, kann aber auch Anforderungen an eine Enterprise-Architektur erfüllen.

Warum Flight wählen?

Videoubersicht

Einfach genug, oder?
Mehr erfahren über Flight in der Dokumentation!

Schnellstart

Für eine schnelle, grundlegende Installation installieren Sie es mit Composer:

composer require flightphp/core

Oder laden Sie ein ZIP des Repos hier herunter. Dann haben Sie eine grundlegende index.php-Datei wie folgt:

<?php

// wenn mit Composer installiert
require 'vendor/autoload.php';
// oder wenn 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. Führen Sie diese Datei mit php -S localhost:8000 aus und besuchen Sie http://localhost:8000 in Ihrem Browser, um die Ausgabe zu sehen.

Skeleton/Boilerplate-App

Es gibt ein Beispiel-App, um Ihr Projekt mit Flight zu starten. Sie hat eine strukturierte Layout, grundlegende Konfigurationen und behandelt Composer-Skripte direkt ab dem Start! Schauen Sie sich flightphp/skeleton für ein fertiges Projekt an, oder besuchen Sie die examples-Seite für Inspiration. Wollen Sie sehen, wie KI passt? Erkunden Sie KI-gestützte Beispiele.

Installation der Skeleton-App

Sehr einfach!

# Erstellen Sie das neue Projekt
composer create-project flightphp/skeleton my-project/
# Gehen Sie in Ihr neues Projektverzeichnis
cd my-project/
# Starten Sie den lokalen Dev-Server, um sofort loszulegen!
composer start

Es erstellt die Projektstruktur, richtet die Dateien ein, und Sie sind bereit!

Hohe Leistung

Flight ist eines der schnellsten PHP-Frameworks da draußen. Sein leichtes Kern bedeutet weniger Overhead und mehr Geschwindigkeit – perfekt für traditionelle Apps und moderne KI-gestützte Projekte. Sie können alle Benchmarks auf TechEmpower sehen.

Sehen Sie sich den Benchmark unten mit einigen anderen beliebten PHP-Frameworks an.

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

Neugierig, wie es mit KI umgeht? Entdecken Sie, wie Flight die Arbeit mit Ihrem favorisierten Coding-LLM einfach macht!

Community

Wir sind im Matrix Chat

Matrix

Und Discord

Beitrag

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

  1. Tragen Sie zum Kern-Framework bei, indem Sie das core repository besuchen.
  2. Helfen Sie, die Docs zu verbessern! Diese Dokumentations-Website ist auf Github gehostet. Wenn Sie einen Fehler entdecken oder etwas verbessern wollen, reichen Sie gerne einen Pull-Request ein. Wir lieben Updates und neue Ideen – besonders rund um KI und neue Technologien!

Anforderungen

Flight erfordert PHP 7.4 oder höher.

Hinweis: PHP 7.4 wird unterstützt, weil zum Zeitpunkt der Erstellung (2024) PHP 7.4 die Standardversion für einige LTS-Linux-Distributionen ist. Eine Zwangsumstellung auf PHP >8 würde für diese Benutzer Probleme 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-In-Datei-Caching-Klasse, geforkt von Wruczek/PHP-File-Cache

Vorteile

Diese Dokumentationsseite verwendet diese Bibliothek, um jede der Seiten zu cachen!

Klicken Sie hier, um den Code anzusehen.

Installation

Installieren Sie über composer:

composer require flightphp/cache

Verwendung

Die Verwendung ist ziemlich unkompliziert. 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 anderswo in Ihrer App gesetzt wird
    $cache->setDevMode(ENVIRONMENT === 'development');
});

Einen Cache-Wert abrufen

Sie verwenden die get()-Methode, um einen gecachten Wert abzurufen. Wenn Sie eine Bequemlichkeitsmethode wollen, die den Cache erneuert, 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"); // return data to be cached
}, 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 set()-Methode, 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 delete()-Methode, um einen Wert im Cache zu löschen.

Flight::cache()->delete('simple-cache-test');

Überprüfen, ob ein Cache-Wert existiert

Sie verwenden die exists()-Methode, um zu überprüfen, ob ein Wert im Cache existiert.

if(Flight::cache()->exists('simple-cache-test')) {
    // etwas tun
}

Den Cache leeren

Sie verwenden die flush()-Methode, um den gesamten Cache zu leeren.

Flight::cache()->flush();

Metadaten mit Cache abrufen

Wenn Sie Timestamps 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"); // return data to be cached
}, 10, true); // true = return with metadata
// oder
$data = $cache->get("simple-cache-meta-test", true); // true = return with metadata

/*
Beispiel für einen gecachten Eintrag, der mit Metadaten abgerufen wird:
{
    "time":1511667506, <-- save unix timestamp
    "expire":10,       <-- expire time in seconds
    "data":"04:38:26", <-- unserialized data
    "permanent":false
}

Mit Metadaten können wir z. B. berechnen, wann der Eintrag gespeichert wurde oder wann er abläuft
Wir können auch auf die Daten selbst mit dem "data"-Schlüssel zugreifen
*/

$expiresin = ($data["time"] + $data["expire"]) - time(); // get unix timestamp when data expires and subtract current timestamp from it
$cacheddate = $data["data"]; // we access the data itself with the "data" key

echo "Latest cache save: $cacheddate, expires in $expiresin seconds";

Dokumentation

Besuchen Sie https://github.com/flightphp/cache, um den Code anzusehen. Stellen Sie sicher, dass Sie den examples-Ordner für zusätzliche Möglichkeiten zur Verwendung des Caches ansehen.

Awesome-plugins/permissions

FlightPHP/Berechtigungen

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. Mit diesem Modul können Sie Berechtigungen für jede Rolle definieren und dann ü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 los geht's!

Verwendung

Zuerst müssen Sie Ihre Berechtigungen einrichten, dann teilen Sie Ihrer App mit, was die Berechtigungen bedeuten. Letztendlich prüfen Sie Ihre Berechtigungen mit $Permissions->has(), ->can() oder is(). has() und can() haben die gleiche Funktionalität, sind jedoch unterschiedlich benannt, um Ihren Code leichter lesbar zu machen.

Grundbeispiel

Angenommen, Sie haben in Ihrer Anwendung eine Funktion, die überprüft, ob ein Benutzer angemeldet ist. Sie können ein Berechtigungsobjekt wie folgt erstellen:

// index.php
require 'vendor/autoload.php';

// etwas Code

// dann haben Sie wahrscheinlich etwas, das Ihnen mitteilt, was die aktuelle Rolle der Person ist
// wahrscheinlich haben Sie etwas, bei dem Sie die aktuelle Rolle abrufen
// aus einer Session-Variablen, die dies definiert
// nachdem sich jemand angemeldet hat, andernfalls haben sie die Rolle 'Gast' oder 'Öffentlich'.
$current_role = 'admin';

// Berechtigungen einrichten
$permission = new \flight\Permission($current_role);
$permission->defineRule('eingeloggt', function($current_role) {
    return $current_role !== 'gast';
});

// Sie werden dieses Objekt wahrscheinlich in Flight persistieren wollen
Flight::set('berechtigung', $permission);

Dann in einem Controller irgendwo haben Sie möglicherweise so etwas.

<?php

// irgendein Controller
class EinController {
    public function eineAktion() {
        $permission = Flight::get('berechtigung');
        if ($permission->has('eingeloggt')) {
            // etwas machen
        } else {
            // etwas anderes machen
        }
    }
}

Sie können dies auch verwenden, um zu verfolgen, ob sie Berechtigungen haben, um in Ihrer Anwendung etwas zu tun. Zum Beispiel, wenn Sie eine Möglichkeit haben, dass Benutzer Beiträge in Ihrer Software interagieren können, können Sie überprüfen, ob sie Berechtigungen haben, bestimmte Aktionen auszuführen.

$current_role = 'admin';

// Berechtigungen einrichten
$permission = new \flight\Permission($current_role);
$permission->defineRule('beitrag', function($current_role) {
    if($current_role === 'admin') {
        $permissions = ['erstellen', 'lesen', 'aktualisieren', 'löschen'];
    } else if($current_role === 'editor') {
        $permissions = ['erstellen', 'lesen', 'aktualisieren'];
    } else if($current_role === 'autor') {
        $permissions = ['erstellen', 'lesen'];
    } else if($current_role === 'mitwirkender') {
        $permissions = ['erstellen'];
    } else {
        $permissions = [];
    }
    return $permissions;
});
Flight::set('berechtigung', $permission);

Dann irgendwo in einem Controller...

class BeitragController {
    public function erstellen() {
        $permission = Flight::get('berechtigung');
        if ($permission->can('beitrag.erstellen')) {
            // etwas machen
        } else {
            // etwas anderes machen
        }
    }
}

Abhängigkeiten injizieren

Sie können Abhängigkeiten in den Closure einfügen, die die Berechtigungen definieren. Dies ist nützlich, wenn Sie eine Art Schalter, ID oder einen anderen Datenpunkt haben, gegen den Sie prüfen möchten. Das Gleiche gilt für Klassen->Methoden-Aufrufe, außer dass Sie die Argumente in der Methode definieren.

Closures

$Permission->defineRule('bestellung', function(string $current_role, MyDependency $MyDependency = null) {
    // ... code
});

// in Ihrer Controllerdatei
public function bestellungErstellen() {
    $MyDependency = Flight::myDependency();
    $permission = Flight::get('berechtigung');
    if ($permission->can('bestellung.erstellen', $MyDependency)) {
        // etwas machen
    } else {
        // etwas anderes machen
    }
}

Klassen

namespace MeinApp;

class Berechtigungen {

    public function bestellung(string $current_role, MyDependency $MyDependency = null) {
        // ... code
    }
}

Verknüpfung zum Setzen von Berechtigungen mit Klassen

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 etwas Ähnliches wie folgt tun:

<?php

// Startcode
$Berechtigungen = new \flight\Permission($current_role);
$Berechtigungen->defineRule('bestellung', 'MeinApp\Berechtigungen->bestellung');

// myapp/Berechtigungen.php
namespace MeinApp;

class Berechtigungen {

    public function bestellung(string $current_role, int $benutzer_id) {
        // Annehmen, dass Sie dies im Voraus eingerichtet haben
        /** @var \flight\database\PdoWrapper $db */
        $db = Flight::db();
        $erlaubte_berechtigungen = [ 'lesen' ]; // jeder kann eine Bestellung einsehen
        if($current_role === 'manager') {
            $erlaubte_berechtigungen[] = 'erstellen'; // Manager können Bestellungen erstellen
        }
        $ein_anderer_spezieller_schalter_aus_db = $db->fetchField('SELECT ein_anderer_spezieller_schalter FROM einstellungen WHERE id = ?', [ $benutzer_id ]);
        if($ein_anderer_spezieller_schalter_aus_db) {
            $erlaubte_berechtigungen[] = 'aktualisieren'; // Wenn der Benutzer einen speziellen Schalter hat, kann er Bestellungen aktualisieren
        }
        if($current_role === 'admin') {
            $erlaubte_berechtigungen[] = 'löschen'; // Admins können Bestellungen löschen
        }
        return $erlaubte_berechtigungen;
    }
}

Der interessante Teil 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 kartieren. Also, wenn Sie eine Methode namens bestellung() und eine Methode namens unternehmen() haben, werden diese automatisch zugeordnet, sodass Sie einfach $Berechtigungen->has('bestellung.lesen') oder $Berechtigungen->has('unternehmen.lesen') ausführen können und es funktioniert. Das Definieren davon ist sehr schwierig, bleiben Sie also bei mir hier. Sie müssen nur dies tun:

Erstellen Sie die Berechtigungsklasse, die Sie zusammenfassen möchten.

class MeineBerechtigungen {
    public function bestellung(string $current_role, int $bestellungs_id = 0): array {
        // Code zur Bestimmung von Berechtigungen
        return $berechtigungen_array;
    }

    public function unternehmen(string $current_role, int $unternehmen_id): array {
        // Code zur Bestimmung von Berechtigungen
        return $berechtigungen_array;
    }
}

Dann machen Sie die Berechtigungen mit Hilfe dieser Bibliothek auffindbar.

$Berechtigungen = new \flight\Permission($current_role);
$Berechtigungen->defineRulesFromClassMethods(MeineApp\Berechtigungen::class);
Flight::set('berechtigungen', $Berechtigungen);

Rufen Sie schließlich die Berechtigung in Ihrem Code auf, um zu überprüfen, ob der Benutzer berechtigt ist, eine bestimmte Berechtigung auszuführen.

class EinController {
    public function bestellungErstellen() {
        if(Flight::get('berechtigungen')->can('bestellung.erstellen') === false) {
            die('Sie können keine Bestellung erstellen. Entschuldigung!');
        }
    }
}

Zwischenspeicherung

Um die Zwischenspeicherung zu aktivieren, sehen Sie sich die einfache wruczak/phpfilecache Bibliothek an. Ein Beispiel zur Aktivierung finden Sie unten.


// dieses $app kann Teil Ihres Codes sein oder
// Sie können einfach null übergeben und es wird
// aus Flight::app() im Konstruktor abgerufen
$app = Flight::app();

// Derzeit akzeptiert es dies als Dateipuffer. Andere können in Zukunft leicht hinzugefügt werden.
$Cache = new Wruczek\PhpFileCache\PhpFileCache;

$Berechtigungen = new \flight\Permission($current_role, $app, $Cache);
$Berechtigungen->defineRulesFromClassMethods(MeineApp\Berechtigungen::class, 3600); // 3600 gibt an, wie viele Sekunden diese Zwischenspeicherung gültig ist. Lassen Sie dies weg, um die Zwischenspeicherung nicht zu verwenden

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

Startbahn

Startbahn ist eine CLI-Anwendung, die Ihnen dabei hilft, Ihre Flight-Anwendungen zu verwalten. Sie kann Controller generieren, alle Routen anzeigen und mehr. Sie basiert auf der ausgezeichneten adhocore/php-cli Bibliothek.

Klicken Sie hier, um den Code anzusehen.

Installation

Mit Composer installieren.

composer require flightphp/startbahn

Grundkonfiguration

Das erste Mal, wenn Sie Startbahn ausführen, wird es Sie durch einen Einrichtungsprozess führen und eine .runway.json-Konfigurationsdatei im Stammverzeichnis Ihres Projekts erstellen. Diese Datei wird einige notwendige Konfigurationen enthalten, damit Startbahn ordnungsgemäß funktioniert.

Verwendung

Startbahn hat mehrere Befehle, die Sie verwenden können, um Ihre Flight-Anwendung zu verwalten. Es gibt zwei einfache Möglichkeiten, Startbahn zu verwenden.

  1. Wenn Sie das Grundgerüstprojekt verwenden, können Sie php startbahn [Befehl] im Stammverzeichnis Ihres Projekts ausführen.
  2. Wenn Sie Startbahn als über Composer installiertes Paket verwenden, können Sie vendor/bin/startbahn [Befehl] im Stammverzeichnis Ihres Projekts ausführen.

Für jeden Befehl können Sie die --help-Flagge übergeben, um weitere Informationen zur Verwendung des Befehls zu erhalten.

php startbahn routes --help

Hier sind ein paar Beispiele:

Einen Controller generieren

Basierend auf der Konfiguration in Ihrer .runway.json-Datei wird der Standardort einen Controller für Sie im app/controllers/ Verzeichnis generieren.

php startbahn make:controller MeinController

Einen Aktiven Datensatz-Model generieren

Basierend auf der Konfiguration in Ihrer .runway.json-Datei wird der Standardort einen Controller für Sie im app/records/ Verzeichnis generieren.

php startbahn make:record benutzer

Wenn Sie zum Beispiel die benutzer Tabelle mit dem folgenden Schema haben: id, name, email, created_at, updated_at, wird eine Datei ähnlich der folgenden in der app/records/BenutzerDatensatz.php erstellt:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * ActiveRecord-Klasse für die benutzer Tabelle.
 * @link https://docs.flightphp.com/awesome-plugins/active-record
 * 
 * @property int $id
 * @property string $name
 * @property string $email
 * @property string $created_at
 * @property string $updated_at
 * // Sie können hier auch Beziehungen hinzufügen, sobald Sie sie im $relations-Array definieren
 * @property CompanyRecord $company Beispiel für eine Beziehung
 */
class BenutzerDatensatz erstreckt sich über \flight\ActiveRecord
{
    /**
     * @var array $relations Setzen Sie die Beziehungen für das Modell
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [];

    /**
     * Konstruktor
     * @param gemischt $datenbankverbindung Die Verbindung zur Datenbank
     */
    public function __construct($datenbankverbindung)
    {
        parent::__construct($datenbankverbindung, 'benutzer');
    }
}

Alle Routen anzeigen

Dies wird alle Routen anzeigen, die derzeit bei Flight registriert sind.

php startbahn routes

Wenn Sie nur bestimmte Routen anzeigen möchten, können Sie eine Flagge übergeben, um die Routen zu filtern.

# Nur GET-Routen anzeigen
php startbahn routes --get

# Nur POST-Routen anzeigen
php startbahn routes --post

# usw.

Anpassen von Startbahn

Wenn Sie entweder ein Paket für Flight erstellen oder Ihre eigenen benutzerdefinierten Befehle in Ihr Projekt integrieren möchten, können Sie dies tun, indem Sie ein src/befehle/, flight/befehle/, app/befehle/ oder befehle/ Verzeichnis für Ihr Projekt/Paket erstellen.

Um einen Befehl zu erstellen, erweitern Sie einfach die Klasse AbstractBaseCommand und implementieren Sie mindestens eine __construct-Methode und eine ausführen-Methode.

<?php

declare(strict_types=1);

namespace flight\commands;

Klasse BeispielBefehl erweitert AbstractBaseCommand
{
    /**
     * Konstruieren
     *
     * @param array<string, gemischt> $konfig JSON-Konfiguration aus .runway-config.json
     */
    public function __construct(array $konfig)
    {
        parent::__construct('make:beispiel', 'Erstellt ein Beispiel für die Dokumentation', $konfig);
        $this->argument('<lustiges-gif>', 'Der Name des lustigen Gifs');
    }

    /**
     * Führt die Funktion aus
     *
     * @return Leer
     */
    public function execute(string $controller)
    {
        $io = $this->app()->io();

        $io->info('Beispiel wird erstellt...');

        // Hier etwas machen

        $io->ok('Beispiel erstellt!');
    }
}

Siehe die adhocore/php-cli Dokumentation für weitere Informationen, wie Sie Ihre eigenen benutzerdefinierten Befehle in Ihre Flight-Anwendung integrieren können!

Awesome-plugins/tracy_extensions

Tracy Flight Panel Extensions

Dies ist eine Sammlung von Erweiterungen, um die Arbeit mit Flight etwas reicher zu gestalten.

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 Sie sind startklar!

Configuration

Es gibt sehr wenig Konfiguration, die Sie vornehmen müssen, um damit zu beginnen. Sie müssen den Tracy-Debugger vor der Verwendung dieser https://tracy.nette.org/en/guide initialisieren:

<?php

use Tracy\Debugger;
use flight\debug\tracy\TracyExtensionLoader;

// bootstrap code
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// You may need to specify your environment with Debugger::enable(Debugger::DEVELOPMENT)

// if you use database connections in your app, there is a 
// required PDO wrapper to use ONLY IN DEVELOPMENT (not production please!)
// It has the same parameters as a regular PDO connection
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// or if you attach this to the Flight framework
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// now whenever you make a query it will capture the time, query, and parameters

// This connects the dots
if(Debugger::$showBar === true) {
    // This needs to be false or Tracy can't actually render :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// more code

Flight::start();

Additional Configuration

Session Data

If you have a custom session handler (such as ghostff/session), you can pass any array of session data to Tracy and it will automatically output it for you. You pass it in with the session_data key in the second parameter of the TracyExtensionLoader constructor.


use Ghostff\Session\Session;
// or use flight\Session;

require 'vendor/autoload.php';

$app = Flight::app();

$app->register('session', Session::class);

if(Debugger::$showBar === true) {
    // This needs to be false or Tracy can't actually render :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// routes and other things...

Flight::start();

Latte

PHP 8.1+ is required for this section.

If you have Latte installed in your project, Tracy has a native integration with Latte to analyze your templates. You simple register the extension with your Latte instance.


require 'vendor/autoload.php';

$app = Flight::app();

$app->map('render', function($template, $data, $block = null) {
    $latte = new Latte\Engine;

    // other configurations...

    // only add the extension if Tracy Debug Bar is enabled
    if(Debugger::$showBar === true) {
        // this is where you add the Latte Panel to Tracy
        $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
    }

    $latte->render($template, $data, $block);
});

Awesome-plugins/apm

FlightPHP APM Dokumentation

Willkommen bei FlightPHP APM – dem persönlichen Performance-Coach für Ihre App! Dieser Leitfaden ist Ihre Roadmap zur Einrichtung, Nutzung und Beherrschung der Application Performance Monitoring (APM) mit FlightPHP. Ob Sie langsame Anfragen aufspüren oder einfach nur Latency-Diagramme analysieren möchten, wir haben Sie abgedeckt. Lassen Sie uns Ihre App schneller machen, Ihre Nutzer glücklicher und Ihre Debugging-Sitzungen zu einem Kinderspiel!

Sehen Sie sich eine Demo des Dashboards für die Flight Docs Site an.

FlightPHP APM

Warum APM wichtig ist

Stellen Sie sich vor: Ihre App ist ein volles Restaurant. Ohne eine Möglichkeit, zu verfolgen, wie lange Bestellungen dauern oder wo die Küche stockt, raten Sie, warum Kunden unzufrieden gehen. APM ist Ihr Sous-Chef – es beobachtet jeden Schritt, von eingehenden Anfragen bis zu Datenbankabfragen, und markiert alles, was Sie verlangsamt. Langsame Seiten verlieren Nutzer (Studien sagen, 53 % verlassen die Seite, wenn sie mehr als 3 Sekunden zum Laden braucht!), und APM hilft Ihnen, diese Probleme bevor sie schmerzen zu erkennen. Es ist proaktive Seelenruhe – weniger „Warum ist das kaputt?“-Momente, mehr „Schau, wie reibungslos 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 wählen (siehe unten). Stellen Sie sicher, dass Ihre PHP-Umgebung die notwendigen Erweiterungen installiert hat (z. B. pdo_sqlite oder pdo_mysql).

Erste Schritte

Hier ist Ihr Schritt-für-Schritt zu APM-Großartigkeit:

1. Registrieren Sie die APM

Fügen Sie das in Ihre index.php oder eine services.php-Datei ein, um mit dem Tracking zu beginnen:

use flight\apm\logger\LoggerFactory;
use flight\Apm;

$ApmLogger = LoggerFactory::create(__DIR__ . '/../../.runway-config.json');
$Apm = new Apm($ApmLogger);
$Apm->bindEventsToFlightInstance($app);

// Wenn Sie eine Datenbankverbindung hinzufügen
// Muss PdoWrapper oder PdoQueryCapture aus Tracy Extensions sein
$pdo = new PdoWrapper('mysql:host=localhost;dbname=example', 'user', 'pass', null, true); // <-- True erforderlich, um Tracking in der APM zu aktivieren.
$Apm->addPdoConnection($pdo);

Was passiert hier?

Pro-Tipp: Sampling Wenn Ihre App beschäftigt ist, könnte das Loggen jeder Anfrage die Dinge überlasten. Verwenden Sie eine Sample-Rate (0.0 bis 1.0):

$Apm = new Apm($ApmLogger, 0.1); // Protokolliert 10 % der Anfragen

Das hält die Performance knackig, während es Ihnen dennoch solide Daten liefert.

2. Konfigurieren Sie es

Führen Sie das aus, um Ihre .runway-config.json zu erstellen:

php vendor/bin/runway apm:init

Was macht das?

Dieser Prozess fragt auch, ob Sie die Migrationen für dieses Setup ausführen möchten. Wenn Sie das zum ersten Mal einrichten, lautet die Antwort ja.

Warum zwei Orte? Rohe Metriken häufen sich schnell an (denken Sie an ungefilterte Logs). Der Worker verarbeitet sie in ein strukturiertes Ziel für das Dashboard. Hält alles ordentlich!

3. Metriken mit dem Worker verarbeiten

Der Worker verwandelt rohe Metriken in dashboard-bereite Daten. Führen Sie ihn einmal aus:

php vendor/bin/runway apm:worker

Was macht er?

Am Laufen halten Für Live-Apps möchten Sie kontinuierliche Verarbeitung. Hier sind Ihre Optionen:

Warum die Mühe? Ohne den Worker ist Ihr Dashboard leer. Es ist die Brücke zwischen rohen Logs und handlungsrelevanten Erkenntnissen.

4. Dashboard starten

Sehen Sie die Vitalwerte Ihrer App:

php vendor/bin/runway apm:dashboard

Was ist das?

Anpassen:

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

Öffnen Sie die URL in Ihrem Browser 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 im Spiel sind. Hier sind ein paar Optionen:

Wollen Sie ein anderes Dashboard?

Sie können Ihr eigenes Dashboard bauen, wenn Sie möchten! Schauen Sie in das Verzeichnis vendor/flightphp/apm/src/apm/presenter für Ideen, wie Sie die Daten für Ihr eigenes Dashboard präsentieren können!

Dashboard-Funktionen

Das Dashboard ist Ihr APM-Hauptquartier – hier ist, was Sie sehen werden:

Extras:

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

Hinzufügen benutzerdefinierter Events

Verfolgen Sie alles – wie einen API-Aufruf oder Zahlungsprozess:

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 erscheint es? In den Anfragen-Details des Dashboards unter „Custom 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 herunterzieht!

Datenbank-Monitoring

Verfolgen Sie PDO-Abfragen so:

use flight\database\PdoWrapper;

$pdo = new PdoWrapper('sqlite:/path/to/db.sqlite', null, null, null, true); // <-- True erforderlich, um Tracking in der APM zu aktivieren.
$Apm->addPdoConnection($pdo);

Was Sie bekommen:

Achtung:

Beispiel-Ausgabe:

Worker-Optionen

Passen Sie den Worker nach Ihrem Geschmack an:

Beispiel:

php vendor/bin/runway apm:worker --daemon --batch_size 100 --timeout 3600

Läuft eine Stunde, verarbeitet 100 Metriken auf einmal.

Request ID in der App

Jede Anfrage hat eine eindeutige Request ID für das Tracking. Sie können diese ID in Ihrer App verwenden, um Logs und Metriken zu korrelieren. Zum Beispiel können Sie die Request ID auf einer Fehlerseite hinzufügen:

Flight::map('error', function($message) {
    // Holen Sie die Request ID aus dem Response-Header X-Flight-Request-Id
    $requestId = Flight::response()->getHeader('X-Flight-Request-Id');

    // Zusätzlich könnten Sie sie aus der Flight-Variable holen
    // 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 der APM upgraden, besteht die Möglichkeit, dass Datenbank-Migrationen ausgeführt werden müssen. Sie können das tun, indem Sie den folgenden Befehl ausführen:

php vendor/bin/runway apm:migrate

Das führt alle benötigten Migrationen aus, 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 möchten diesen Befehl vielleicht während der Nebenzeiten ausführen.

Alte Daten bereinigen

Um Ihre Datenbank ordentlich zu halten, können Sie alte Daten bereinigen. Das ist besonders nützlich, wenn Sie eine beschäftigte App betreiben und die Datenbankgröße handhabbar halten möchten. Sie können das tun, indem Sie den folgenden Befehl ausführen:

php vendor/bin/runway apm:purge

Das 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

Das entfernt alle Daten, die älter als 7 Tage sind, aus der Datenbank.

Fehlerbehebung

Feststecken? Probieren Sie diese aus:

Awesome-plugins/tracy

Tracy

Tracy ist ein erstaunlicher Fehlerhandler, der mit Flight verwendet werden kann. Es verfügt über eine Reihe von Panels, die Ihnen bei der Fehlerbehebung Ihrer Anwendung helfen können. Es ist auch sehr einfach zu erweitern und eigene Panels hinzuzufügen. Das Flight-Team hat speziell für Flight-Projekte mit dem flightphp/tracy-extensions Plugin einige Panels erstellt.

Installation

Installiere es mit Composer. Und in der Tat möchten Sie dies ohne die Entwicklerversion installieren, da Tracy mit einem Produktionsfehlerbehandlungskomponente geliefert wird.

composer require tracy/tracy

Grundkonfiguration

Es gibt einige grundlegende Konfigurationsoptionen, um loszulegen. Weitere Informationen dazu finden Sie in der Tracy-Dokumentation.


require 'vendor/autoload.php';

use Tracy\Debugger;

// Aktiviere Tracy
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 bereitstellen

// Hier werden Fehler und Ausnahmen protokolliert. Stellen Sie sicher, dass dieses Verzeichnis vorhanden ist 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 Inhaltslänge nicht von Flight festgelegt werden

    // Dies ist spezifisch für die Tracy-Erweiterung für Flight, wenn Sie diese eingeschlossen 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 Aktive Datensätze

Ein aktiver Datensatz ist die Zuordnung einer Datenbankentität zu einem PHP-Objekt. Einfach gesagt, wenn Sie eine Tabelle für Benutzer in Ihrer Datenbank haben, können Sie eine Zeile in dieser Tabelle in eine User-Klasse und ein $user-Objekt in Ihrem Code übersetzen. Siehe ein einfaches Beispiel.

Klicken Sie hier für das Repository auf GitHub.

Einfaches Beispiel

Angenommen, Sie haben die folgende Tabelle:

CREATE TABLE users (
    id INTEGER PRIMARY KEY, 
    name TEXT, 
    password TEXT 
);

Jetzt können Sie eine neue Klasse einrichten, um diese Tabelle darzustellen:

/**
 * Eine ActiveRecord-Klasse ist normalerweise im 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)
    {
        // so können Sie es einrichten
        parent::__construct($database_connection, 'users');
        // oder so
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

Jetzt 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('ein cooles Passwort');
$user->insert();
// oder $user->save();

echo $user->id; // 1

$user->name = 'Joseph Mamma';
$user->password = password_hash('ein anderes cooles Passwort!!!');
$user->insert();
// $user->save() kann hier nicht verwendet werden, da es als Update betrachtet wird!

echo $user->id; // 2

Und es war so einfach, einen neuen Benutzer hinzuzufügen! Jetzt, wo es eine Benutzerzeile in der Datenbank gibt, wie ziehen Sie sie heraus?

$user->find(1); // finde id = 1 in der Datenbank und gebe sie zurück.
echo $user->name; // 'Bobby Tables'

Und was ist, wenn Sie alle Benutzer finden möchten?

$users = $user->findAll();

Wie wäre es mit einer bestimmten Bedingung?

$users = $user->like('name', '%mamma%')->findAll();

Sehen Sie, wie viel Spaß das macht? Lassen Sie uns das 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. Vollständig Ihnen überlassen.

Eigenständig

Stellen Sie sicher, dass Sie eine PDO-Verbindung an den Konstruktor weitergeben.

$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 Ihre Datenbankverbindung im Konstruktor einstellen? Siehe Datenbankverbindungsmanagement für weitere Ideen!

Als Methode in Flight registrieren

Wenn Sie das Flight PHP Framework verwenden, können Sie die ActiveRecord-Klasse als Dienst registrieren, aber das müssen Sie ehrlich gesagt nicht.

Flight::register('user', 'User', [ $pdo_connection ]);

// Dann können Sie es so in einem Controller, einer Funktion usw. 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 wird eine neue Klasse im Verzeichnis app/records/ als UserRecord.php mit folgendem Inhalt erstellen:

<?php

declare(strict_types=1);

namespace app\records;

/**
 * ActiveRecord-Klasse für die Benutzertabelle.
 * @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 Legen Sie die Beziehungen für das Modell fest
     *   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 irgendeiner Art übergeben, wird eine Abfrage mit dem Primärschlüssel mit diesem Wert durchgeführt. Wenn nichts übergeben wird, wird einfach der erste Datensatz in der Tabelle gefunden.

Zusätzlich können Sie ihm andere Hilfsmethoden übergeben, um Ihre Tabelle abzufragen.

// finde einen Datensatz mit vorher festgelegten Bedingungen
$user->notNull('password')->orderBy('id DESC')->find();

// finde einen Datensatz nach einer bestimmten 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 hydratisiert (aus der Datenbank abgerufen) wurde.

$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 (wie eine UUID) haben, 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 können den Primärschlüssel automatisch durch Ereignisse generieren lassen.

class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
        // Sie können auch so anstelle des obigen Arrays den Primärschlüssel festlegen.
        $this->primaryKey = 'uuid';
    }

    protected function beforeInsert(self $self) {
        $self->uuid = uniqid(); // oder wie auch immer Sie Ihre einzigartigen IDs generieren müssen
    }
}

Wenn Sie den Primärschlüssel vor dem Einfügen nicht festlegen, wird er auf den rowid gesetzt und die Datenbank wird ihn für Sie generieren, aber es wird nicht gespeichert, da dieses Feld möglicherweise nicht in Ihrer Tabelle vorhanden ist. Aus diesem Grund wird empfohlen, das Ereignis zu verwenden, um dies automatisch für Sie zu verwalten.

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 wird er eingefügt.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->save();

Hinweis: Wenn Sie Beziehungen, die in der Klasse definiert sind, haben, werden diese ebenfalls rekursiv gespeichert, wenn sie definiert, instanziiert und überarbeitete Daten zum Aktualisieren aufweisen. (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 zuvor eine Suche durchführen.

$user->like('name', 'Bob%')->delete();

dirty(array $dirty = []): ActiveRecord

"Schmutzige" Daten bezieht sich auf die Daten, die in einem Datensatz geändert wurden.

$user->greaterThan('id', 0)->orderBy('id desc')->find();

// zu diesem Zeitpunkt ist nichts "schmutzig".

$user->email = 'test@example.com'; // jetzt wird die E-Mail als "schmutzig" betrachtet, da sie geändert wurde.
$user->update();
// jetzt gibt es keine Daten, die schmutzig sind, da sie aktualisiert und in der Datenbank gespeichert wurden

$user->password = password_hash('newpassword'); // jetzt ist dies schmutzig
$user->dirty(); // Nichts zu übergebendes löscht alle schmutzigen Einträge.
$user->update(); // nichts wird aktualisiert, da nichts als schmutzig erfasst wurde.

$user->dirty([ 'name' => 'etwas', 'password' => password_hash('ein anderes Passwort') ]);
$user->update(); // sowohl Name als auch Passwort werden aktualisiert.

copyFrom(array $data): ActiveRecord (v0.4.0)

Dies ist ein Alias für die Methode dirty(). Es ist etwas klarer, was Sie tun.

$user->copyFrom([ 'name' => 'etwas', 'password' => password_hash('ein anderes Passwort') ]);
$user->update(); // sowohl Name als auch Passwort 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 ursprünglichen Zustand zurück. Dies ist wirklich gut in Schleifenverhalten zu verwenden. Wenn Sie true übergeben, werden auch die Abfragedaten zurückgesetzt, 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(); // beginne mit einem sauberen Zustand
    $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 das SQL abrufen, das erstellt wurde und für Debugging-Zwecke verwenden.

SQL-Abfragemethoden

select(string $field1 [, string $field2 ... ])

Sie können nur einige der Spalten in einer Tabelle auswählen, wenn Sie möchten (es ist leistungsfähiger bei wirklich breiten Tabellen mit vielen Spalten)

$user->select('id', 'name')->find();

from(string $table)

Technisch können Sie auch 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 eine andere Tabelle in der Datenbank verknüpfen.

$user->join('contacts', 'contacts.user_id = users.id')->find();

where(string $where_conditions)

Sie können einige benutzerdefinierte WHERE-Argumente festlegen (Sie können keine Parameter in dieser WHERE-Anweisung festlegen)

$user->where('id=1 AND name="demo"')->find();

Sicherheitsnotiz - Sie könnten versucht sein, etwas wie $user->where("id = '{$id}' AND name = '{$name}'")->find(); zu tun. BITTE MACHEN SIE DAS NICHT!!! Dies ist anfällig für das, was als SQL-Injection-Angriffe bekannt ist. Es gibt viele Artikel online, bitte googeln Sie "sql injection attacks php" und Sie werden viele Artikel zu diesem Thema finden. Der richtige Weg, dies mit dieser Bibliothek zu behandeln, besteht darin, anstelle dieser where()-Methode etwas mehr wie $user->eq('id', $id)->eq('name', $name)->find(); zu tun. Wenn Sie dies absolut tun müssen, hat die PDO-Bibliothek $pdo->quote($var), um es für Sie zu escapen. Erst nachdem Sie quote() verwenden, 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 ganze Zahl angegeben wird, wird sie wie in SQL offset und limit.

$user->orderby('name DESC')->limit(0, 10)->findAll();

WHERE-Bedingungen

equal(string $field, mixed $value) / eq(string $field, mixed $value)

Wo field = $value

$user->eq('id', 1)->find();

notEqual(string $field, mixed $value) / ne(string $field, mixed $value)

Wo field <> $value

$user->ne('id', 1)->find();

isNull(string $field)

Wo field IS NULL

$user->isNull('id')->find();

isNotNull(string $field) / notNull(string $field)

Wo field IS NOT NULL

$user->isNotNull('id')->find();

greaterThan(string $field, mixed $value) / gt(string $field, mixed $value)

Wo field > $value

$user->gt('id', 1)->find();

lessThan(string $field, mixed $value) / lt(string $field, mixed $value)

Wo field < $value

$user->lt('id', 1)->find();

greaterThanOrEqual(string $field, mixed $value) / ge(string $field, mixed $value) / gte(string $field, mixed $value)

Wo field >= $value

$user->ge('id', 1)->find();

lessThanOrEqual(string $field, mixed $value) / le(string $field, mixed $value) / lte(string $field, mixed $value)

Wo field <= $value

$user->le('id', 1)->find();

like(string $field, mixed $value) / notLike(string $field, mixed $value)

Wo field LIKE $value oder field NOT LIKE $value

$user->like('name', 'de')->find();

in(string $field, array $values) / notIn(string $field, array $values)

Wo field IN($value) oder field NOT IN($value)

$user->in('id', [1, 2])->find();

between(string $field, array $values)

Wo field BETWEEN $value AND $value1

$user->between('id', [1, 2])->find();

ODER-Bedingungen

Es ist möglich, Ihre Bedingungen in einer ODER-Anweisung einzuwickeln. Dies erfolgt entweder durch die Methoden startWrap() und endWrap() oder indem Sie den 3. Parameter der Bedingung nach dem Feld und dem Wert ausfüllen.

// Methode 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Dies wird ausgewertet zu `id = 1 AND (name = 'demo' OR name = 'test')`

// Methode 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Dies wird ausgewertet zu `id = 1 OR name = 'demo'`

Beziehungen

Sie können mit dieser Bibliothek mehrere Arten von Beziehungen festlegen. Sie können Eins-zu-viele- und Eins-zu-eins-Beziehungen zwischen Tabellen festlegen. Dies erfordert eine kleine zusätzliche Einrichtung in der Klasse im Voraus.

Das Festlegen des $relations-Arrays ist nicht schwer, aber die richtige Syntax zu erraten, kann verwirrend sein.

protected array $relations = [
    // Sie können den Schlüssel benennen, wie Sie möchten. 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 Beziehungstyp

        // 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 zur Info, dies verbindet sich ebenfalls nur mit dem Primärschlüssel des "anderen" Modells

        // optional
        [ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // zusätzliche Bedingungen, die Sie beim Verknüpfen der Beziehung möchten
        // $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))

        // optional
        'back_reference_name' // dies ist, wenn Sie diese Beziehung wieder auf sich selbst zurückverweisen 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');
    }
}

Jetzt haben wir die Referenzen eingerichtet, sodass wir sie sehr einfach verwenden können!

$user = new User($pdo_connection);

// finde den aktuellsten Benutzer.
$user->notNull('id')->orderBy('id desc')->find();

// hole Kontakte mithilfe der Beziehung:
foreach($user->contacts as $contact) {
    echo $contact->id;
}

// oder wir können es andersherum machen.
$contact = new Contact();

// finde einen Kontakt
$contact->find();

// hole Benutzer mithilfe der Beziehung:
echo $contact->user->name; // dies ist der Benutzername

Ganz schön cool, oder?

Benutzerdefinierte Daten festlegen

Manchmal müssen Sie etwas Einzigartiges an Ihrem ActiveRecord anhängen, z. B. eine benutzerdefinierte Berechnung, die es einfacher machen könnte, einfach an das Objekt anzuhängen, das dann an beispielsweise eine Vorlage übergeben wird.

setCustomData(string $field, mixed $value)

Sie hängen die benutzerdefinierten Daten mit der Methode setCustomData() an.

$user->setCustomData('page_view_count', $page_view_count);

Und dann können Sie einfach auf sie zugreifen wie auf eine normale Objekt-Eigenschaft.

echo $user->page_view_count;

Ereignisse

Eine weitere großartige Funktion dieser Bibliothek sind die Ereignisse. Ereignisse werden zu bestimmten Zeiten ausgelöst, basierend auf bestimmten Methoden, die Sie aufrufen. Sie sind sehr hilfreich, um automatisch Daten für Sie einzurichten.

onConstruct(ActiveRecord $ActiveRecord, array &config)

Dies ist wirklich hilfreich, wenn Sie eine Standardverbindung oder so etwas 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 dies tun, um die Verbindung automatisch festzulegen
        $config['connection'] = Flight::db();
        // oder dies
        $self->transformAndPersistConnection(Flight::db());

        // Sie können auch den Tabellennamen auf diese Weise festlegen.
        $config['table'] = 'users';
    } 
}

beforeFind(ActiveRecord $ActiveRecord)

Dies ist wahrscheinlich nur nützlich, wenn Sie jede Abfrage bei Bedarf 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 Ihre Vorliebe ist
        $self->gte('id', 0); 
    } 
}

afterFind(ActiveRecord $ActiveRecord)

Dies ist wahrscheinlich nützlicher, wenn Sie immer eine Logik ausführen müssen, jedes Mal, wenn dieser Datensatz abgerufen wird. Müssen Sie etwas entschlüsseln? Müssen Sie jedes Mal eine benutzerdefinierte Zählabfrage ausführen (nicht leistungsfähig, aber was soll's)?

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)

Dies ist wahrscheinlich nur nützlich, wenn Sie jede Abfrage bei Bedarf 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 Ihre Vorliebe ist
        $self->gte('id', 0); 
    } 
}

afterFindAll(array<int,ActiveRecord> $results)

Ähnlich wie afterFind(), aber Sie können es auf alle Datensätze anwenden!

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 bei afterFind()
        }
    } 
}

beforeInsert(ActiveRecord $ActiveRecord)

Sehr hilfreich, wenn Sie jedes Mal Standardwerte festlegen müssen.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // einige sinnvolle Standardwerte festlegen
        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 Benutzerfall, 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 möchten
        Flight::cache()->set('most_recent_insert_id', $self->id);
        // oder was auch immer....
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

Sehr hilfreich, wenn Sie jedes Mal Standardwerte festlegen müssen, wenn eine Aktualisierung erfolgt.

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeInsert(self $self) {
        // einige sinnvolle Standardwerte festlegen
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

Vielleicht haben Sie einen Benutzerfall, 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 möchten
        Flight::cache()->set('most_recently_updated_user_id', $self->id);
        // oder was auch immer....
    } 
}

beforeSave(ActiveRecord $ActiveRecord)/afterSave(ActiveRecord $ActiveRecord)

Dies ist nützlich, wenn Sie Ereignisse auslösen möchten, sowohl wenn Einfügungen als auch Aktualisierungen erfolgen. Ich spare Ihnen die lange Erklärung, aber ich bin mir sicher, dass Sie erraten können, 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)

Ich bin mir nicht sicher, was Sie hier tun möchten, aber hier wird nicht geurteilt! Legen Sie los!

class User extends flight\ActiveRecord {

    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users');
    }

    protected function beforeDelete(self $self) {
        echo 'Er war ein tapferer Soldat... :cry-face:';
    } 
}

Verwaltung der Datenbankverbindung

Wenn Sie diese Bibliothek verwenden, können Sie die Datenbankverbindung auf mehrere Arten festlegen. Sie können die Verbindung im Konstruktor festlegen, Sie können sie über eine Konfigurationsvariable $config['connection'] festlegen oder Sie können sie über setDatabaseConnection() (v0.4.1) festlegen.

$pdo_connection = new PDO('sqlite:test.db'); // zum 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, jedes Mal eine $database_connection festzulegen, wenn Sie einen aktiven Datensatz aufrufen, gibt es Möglichkeiten, dies zu umgehen!

// index.php oder bootstrap.php
// Setzen Sie dies als registrierte Klasse in Flight
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 jetzt, keine Argumente erforderlich!
$user = new User();

Hinweis: Wenn Sie planen, Unit-Tests durchzuführen, kann es einige Herausforderungen beim Testen mit dieser Methode geben, aber insgesamt ist es nicht zu schlecht, da Sie Ihre Verbindung mit setDatabaseConnection() oder $config['connection'] injizieren können.

Wenn Sie die Datenbankverbindung aktualisieren müssen, z. B. wenn Sie ein lang laufendes CLI-Skript ausführen und die Verbindung von Zeit zu Zeit aktualisieren müssen, können Sie die Verbindung mit $your_record->setDatabaseConnection($pdo_connection) erneut festlegen.

Beitragen

Bitte tun Sie das. :D

Einrichtung

Wenn Sie einen Beitrag leisten, stellen Sie sicher, dass Sie composer test-coverage ausführen, um 100 % Testabdeckung aufrechtzuerhalten (das ist keine echte Unit-Testabdeckung, mehr wie Integrationstests).

Stellen Sie auch sicher, dass Sie composer beautify und composer phpcs ausführen, um alle 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

Tolle 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, um Ihnen den Einstieg zu erleichtern.

API-Dokumentation

API-Dokumentation ist entscheidend für jede API. Sie hilft Entwicklern zu verstehen, wie sie mit Ihrer API interagieren können und was sie als Rückgabe erwarten können. Es gibt einige Tools, die Ihnen helfen können, API-Dokumentation für Ihre Flight-Projekte zu generieren.

Application Performance Monitoring (APM)

Application Performance Monitoring (APM) ist entscheidend für jede Anwendung. Es hilft Ihnen zu verstehen, wie Ihre Anwendung performt und wo die Engpässe liegen. Es gibt eine Reihe von APM-Tools, die mit Flight verwendet werden können.

Async

Flight ist bereits ein schnelles Framework, aber es mit einem Turbo-Motor auszustatten macht alles noch spaßiger (und herausfordernder)!

Autorisierung/Berechtigungen

Autorisierung und Berechtigungen sind entscheidend für jede Anwendung, die Steuerungen benötigt, um festzulegen, wer auf was zugreifen kann.

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 einige Plugins, die Ihr Debugging-Erlebnis verbessern können.

Datenbanken

Datenbanken sind der Kern der meisten Anwendungen. So speichern und rufen Sie Daten ab. Einige Datenbankbibliotheken sind einfach Wrapper, um Abfragen zu schreiben, und einige sind vollwertige ORMs.

Verschlüsselung

Verschlüsselung ist entscheidend für jede Anwendung, die sensible Daten speichert. Das Verschlüsseln und Entschlüsseln der Daten ist nicht besonders schwer, aber das ordnungsgemäße Speichern des Verschlüsselungsschlüssels kann sein schwierig. Das Wichtigste ist, Ihren Verschlüsselungsschlüssel niemals in einem öffentlichen Verzeichnis zu speichern oder ihn in Ihr Code-Repository zu committen.

Job Queue

Job-Warteschlangen sind wirklich hilfreich, um Aufgaben asynchron zu verarbeiten. Das kann das Versenden 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 beim Aufbau einer Web-Anwendung können Sessions entscheidend für die Aufrechterhaltung des Zustands und Login-Informationen sein.

Templating

Templating ist der Kern jeder Web-Anwendung mit einer UI. 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!

Beitrag

Haben Sie ein Plugin, das Sie teilen möchten? Reichen Sie einen Pull Request ein, 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

Schneller Einstieg?

Sie haben zwei Optionen, um mit einem neuen Flight-Projekt zu starten:

Community-beigetragene Beispiele:

Brauchen Sie Inspiration?

Obwohl diese nicht offiziell von dem Flight-Team gesponsert werden, könnten sie Ihnen Ideen geben, wie Sie Ihre eigenen Projekte strukturieren, die mit Flight aufgebaut sind!

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

Installationsanweisungen

Es gibt einige grundlegende Voraussetzungen, bevor Sie Flight installieren können. Insbesondere benötigen Sie:

  1. Installieren Sie PHP auf Ihrem System
  2. Installieren Sie Composer für das beste Entwicklererlebnis.

Basisinstallation

Wenn Sie Composer verwenden, können Sie den folgenden Befehl ausführen:

composer require flightphp/core

Dies platziert nur die Flight-Kern-Dateien auf Ihrem System. Sie müssen die Projektstruktur definieren, Layout, Abhängigkeiten, Konfigurationen, Autoloading usw. Diese Methode stellt sicher, dass keine anderen Abhängigkeiten außer Flight installiert werden.

Sie können die Dateien auch direkt herunterladen und sie in Ihr Web-Verzeichnis extrahieren.

Empfohlene Installation

Es wird dringend empfohlen, mit der flightphp/skeleton-App für alle neuen Projekte zu beginnen. Die Installation ist kinderleicht.

composer create-project flightphp/skeleton my-project/

Dies richtet Ihre Projektstruktur ein, konfiguriert Autoloading mit Namespaces, richtet eine Konfiguration ein und stellt andere Tools wie Tracy, Tracy Extensions und Runway bereit.

Konfigurieren Sie Ihren Webserver

Eingebauter PHP-Entwicklungsserver

Dies ist bei weitem der einfachste Weg, um loszulegen. Sie können den eingebauten Server verwenden, um Ihre Anwendung auszuführen, und sogar SQLite für eine Datenbank verwenden (solange sqlite3 auf Ihrem System installiert ist) und fast nichts anderes benötigen! Führen Sie einfach den folgenden Befehl aus, sobald PHP installiert ist:

php -S localhost:8000
# oder mit der Skeleton-App
composer start

Öffnen Sie dann Ihren Browser und gehen Sie zu http://localhost:8000.

Wenn Sie das Dokumentenroot Ihres Projekts zu einem anderen Verzeichnis machen möchten (z. B. Ihr Projekt ist ~/myproject, aber Ihr Dokumentenroot ist ~/myproject/public/), können Sie den folgenden Befehl ausführen, sobald Sie sich im ~/myproject-Verzeichnis befinden:

php -S localhost:8000 -t public/
# mit der Skeleton-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. Falls nicht, googeln Sie, wie Sie Apache auf Ihrem System installieren.

Für Apache bearbeiten Sie Ihre .htaccess-Datei mit dem Folgenden:

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 hinzu RewriteBase /subdir/ direkt nach RewriteEngine On.

Hinweis: Wenn Sie alle Serverdateien schützen möchten, wie eine db- oder env-Datei. 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. Falls nicht, googeln Sie, wie Sie Nginx auf Ihrem System installieren.

Für Nginx fügen Sie das Folgende zu Ihrer Server-Deklaration hinzu:

server {
  location / {
    try_files $uri $uri/ /index.php;
  }
}

Erstellen Sie Ihre index.php-Datei

Wenn Sie eine Basisinstallation durchführen, benötigen Sie etwas Code, um zu starten.

<?php

// Wenn Sie Composer verwenden, laden Sie den Autoloader.
// if you're using Composer, require the autoloader.
require 'vendor/autoload.php';
// wenn Sie Composer nicht verwenden, laden Sie das Framework direkt
// if you're not using Composer, load the framework directly
// require 'flight/Flight.php';

// Definieren Sie dann eine Route und weisen Sie eine Funktion zu, um die Anfrage zu handhaben.
Flight::route('/', function () {
  echo 'hello world!';
});

// Starten Sie schließlich das Framework.
Flight::start();

Mit der Skeleton-App ist dies bereits konfiguriert und in Ihrer app/config/routes.php-Datei gehandhabt. Dienste werden in app/config/services.php konfiguriert.

PHP installieren

Wenn Sie bereits php auf Ihrem System installiert haben, überspringen Sie diese Anweisungen und gehen Sie zum Download-Bereich.

macOS

PHP mit Homebrew installieren

  1. Installieren Sie Homebrew (falls noch nicht 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. Installieren Sie PHP:

    • Installieren Sie die neueste Version:
      brew install php
    • Um eine spezifische Version zu installieren, z. B. PHP 8.1:
      brew tap shivammathur/php
      brew install shivammathur/php/php@8.1
  3. Wechseln Sie zwischen PHP-Versionen:

    • Verknüpfen Sie die aktuelle 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 for Windows und laden Sie die neueste oder eine spezifische Version (z. B. 7.4, 8.0) als nicht-thread-sichere ZIP-Datei herunter.
  2. PHP extrahieren:

    • Extrahieren Sie die heruntergeladene ZIP-Datei nach C:\php.
  3. PHP zum System-PATH hinzufügen:

    • Gehen Sie zu Systemeigenschaften > Umgebungsvariablen.
    • Unter Systemvariablen finden Sie Path und klicken Sie auf Bearbeiten.
    • Fügen Sie den Pfad C:\php (oder wo Sie PHP extrahiert haben) hinzu.
    • Klicken Sie auf OK, um alle Fenster zu schließen.
  4. PHP konfigurieren:

    • Kopieren Sie php.ini-development zu php.ini.
    • Bearbeiten Sie php.ini, um PHP wie benötigt zu konfigurieren (z. B. extension_dir einstellen, Erweiterungen aktivieren).
  5. PHP-Installation überprüfen:

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

Mehrere Versionen von PHP 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 Versionen, indem Sie die System-PATH-Variable anpassen, um auf das gewünschte Versionsverzeichnis zu verweisen.

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 spezifische Version zu installieren, z. B. PHP 8.1:
      sudo apt install php8.1
  3. Zusätzliche Module installieren (optional):

    • Zum Beispiel, um MySQL-Unterstützung zu installieren:
      sudo apt install php8.1-mysql
  4. Wechseln Sie zwischen PHP-Versionen:

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

    • Führen Sie aus:
      php -v

Rocky Linux

PHP mit yum/dnf installieren

  1. Aktivieren Sie das EPEL-Repository:

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

    • 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 spezifische Version zu installieren, z. B. PHP 7.4:
      sudo dnf module install php:remi-7.4
  4. Wechseln Sie zwischen PHP-Versionen:

    • Verwenden Sie den dnf-Modulbefehl:
      sudo dnf module reset php
      sudo dnf module enable php:remi-8.0
      sudo dnf install php
  5. Die 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.