Learn/flight_vs_laravel

Flight vs Laravel

Qu'est-ce que Laravel ?

Laravel est un framework complet qui possède toutes les fonctionnalités avancées et un écosystème axé sur les développeurs impressionnant, mais au prix d'une performance et d'une complexité élevées. L'objectif de Laravel est que le développeur atteigne le plus haut niveau de productivité et que les tâches courantes soient facilitées. Laravel est un excellent choix pour les développeurs qui souhaitent construire une application web complète et d'entreprise. Cela implique toutefois certains compromis, notamment en termes de performance et de complexité. Apprendre les bases de Laravel peut être facile, mais acquérir une maîtrise du framework peut prendre du temps.

Il existe également de nombreux modules Laravel, ce qui fait que les développeurs ont souvent l'impression que la seule façon de résoudre les problèmes est d'utiliser ces modules, alors qu'en réalité, il serait possible d'utiliser une autre bibliothèque ou d'écrire son propre code.

Avantages par rapport à Flight

Inconvénients par rapport à Flight

Learn/migrating_to_v3

Migration vers v3

La compatibilité arrière a été maintenue dans l'ensemble, mais il y a certains changements dont vous devriez être conscient lors de la migration de v2 vers v3. Il y a des changements qui entraient trop en conflit avec les patrons de conception, donc certains ajustements ont dû être faits.

Comportement de mise en tampon de sortie

v3.5.0

Output buffering est le processus par lequel la sortie générée par un script PHP est stockée dans un tampon (interne à PHP) avant d'être envoyée au client. Cela vous permet de modifier la sortie avant qu'elle ne soit envoyée au client.

Dans une application MVC, le Contrôleur est le « gestionnaire » et il gère ce que fait la vue. Avoir une sortie générée en dehors du contrôleur (ou dans le cas de Flight, parfois une fonction anonyme) rompt le patron MVC. Ce changement vise à être plus en ligne avec le patron MVC et à rendre le framework plus prévisible et plus facile à utiliser.

En v2, la mise en tampon de sortie était gérée d'une manière où elle ne fermait pas de manière cohérente son propre tampon de sortie, ce qui rendait les tests unitaires et le streaming plus difficiles. Pour la majorité des utilisateurs, ce changement ne vous affectera peut-être pas réellement. Cependant, si vous affichez du contenu en dehors des callables et des contrôleurs (par exemple dans un hook), vous risquez d'avoir des problèmes. Afficher du contenu dans les hooks, et avant que le framework ne s'exécute réellement, pouvait fonctionner dans le passé, mais cela ne fonctionnera plus à l'avenir.

Où vous pourriez avoir des problèmes

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

// just an example
define('START_TIME', microtime(true));

function hello() {
    echo 'Hello World';
}

Flight::map('hello', 'hello');
Flight::after('hello', function(){
    // this will actually be fine
    echo '<p>This Hello World phrase was brought to you by the letter "H"</p>';
});

Flight::before('start', function(){
    // things like this will cause an error
    echo '<html><head><title>My Page</title></head><body>';
});

Flight::route('/', function(){
    // this is actually just fine
    echo 'Hello World';

    // This should be just fine as well
    Flight::hello();
});

Flight::after('start', function(){
    // this will cause an error
    echo '<div>Your page loaded in '.(microtime(true) - START_TIME).' seconds</div></body></html>';
});

Activation du comportement de rendu v2

Pouvez-vous encore garder votre ancien code tel quel sans faire de réécriture pour le faire fonctionner avec v3 ? Oui, vous le pouvez ! Vous pouvez activer le comportement de rendu v2 en définissant l'option de configuration flight.v2.output_buffering à true. Cela vous permettra de continuer à utiliser l'ancien comportement de rendu, mais il est recommandé de le corriger à l'avenir. En v4 du framework, cela sera supprimé.

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

Flight::set('flight.v2.output_buffering', true);

Flight::before('start', function(){
    // Now this will be just fine
    echo '<html><head><title>My Page</title></head><body>';
});

// more code 

Changements du Dispatcher

v3.7.0

Si vous avez directement appelé des méthodes statiques pour Dispatcher telles que Dispatcher::invokeMethod(), Dispatcher::execute(), etc., vous devrez mettre à jour votre code pour ne plus appeler directement ces méthodes. Dispatcher a été converti pour être plus orienté objet afin que les Conteneurs d'Injection de Dépendances puissent être utilisés plus facilement. Si vous avez besoin d'invoquer une méthode de manière similaire à ce que faisait Dispatcher, vous pouvez manuellement utiliser quelque chose comme $result = $class->$method(...$params); ou call_user_func_array() à la place.

Changements de halt() stop() redirect() et error()

v3.10.0

Le comportement par défaut avant 3.10.0 était d'effacer à la fois les en-têtes et le corps de la réponse. Cela a été changé pour ne vider que le corps de la réponse. Si vous avez besoin d'effacer également les en-têtes, vous pouvez utiliser Flight::response()->clear().

Learn/configuration

Configuration

Aperçu

Flight fournit un moyen simple de configurer divers aspects du framework pour répondre aux besoins de votre application. Certains sont définis par défaut, mais vous pouvez les remplacer au besoin. Vous pouvez également définir vos propres variables pour les utiliser dans toute votre application.

Comprendre

Vous pouvez personnaliser certains comportements de Flight en définissant des valeurs de configuration via la méthode set.

Flight::set('flight.log_errors', true);

Dans le fichier app/config/config.php, vous pouvez voir toutes les variables de configuration par défaut disponibles.

Utilisation de base

Options de configuration Flight

Voici une liste de toutes les options de configuration disponibles :

Configuration du chargeur

Il y a en outre une autre option de configuration pour le chargeur. Cela vous permettra de charger automatiquement les classes avec _ dans le nom de la classe.

// Activer le chargement de classes avec des underscores
// Par défaut true
Loader::$v2ClassLoading = false;

Variables

Flight vous permet de sauvegarder des variables pour qu'elles puissent être utilisées n'importe où dans votre application.

// Sauvegarder votre variable
Flight::set('id', 123);

// Ailleurs dans votre application
$id = Flight::get('id');

Pour voir si une variable a été définie, vous pouvez faire :

if (Flight::has('id')) {
  // Faire quelque chose
}

Vous pouvez effacer une variable en faisant :

// Efface la variable id
Flight::clear('id');

// Efface toutes les variables
Flight::clear();

Note : Le fait que vous puissiez définir une variable ne signifie pas que vous devriez le faire. Utilisez cette fonctionnalité avec parcimonie. La raison est que tout ce qui est stocké ici devient une variable globale. Les variables globales sont mauvaises car elles peuvent être modifiées de n'importe où dans votre application, rendant difficile la traque des bugs. De plus, cela peut compliquer des choses comme les tests unitaires.

Erreurs et exceptions

Toutes les erreurs et exceptions sont capturées par Flight et passées à la méthode error. si flight.handle_errors est défini à true.

Le comportement par défaut est d'envoyer une réponse générique HTTP 500 Internal Server Error avec des informations d'erreur.

Vous pouvez remplacer ce comportement pour vos propres besoins :

Flight::map('error', function (Throwable $error) {
  // Gérer l'erreur
  echo $error->getTraceAsString();
});

Par défaut, les erreurs ne sont pas journalisées sur le serveur web. Vous pouvez activer cela en modifiant la configuration :

Flight::set('flight.log_errors', true);

404 Non trouvé

Quand une URL ne peut pas être trouvée, Flight appelle la méthode notFound. Le comportement par défaut est d'envoyer une réponse HTTP 404 Not Found avec un message simple.

Vous pouvez remplacer ce comportement pour vos propres besoins :

Flight::map('notFound', function () {
  // Gérer non trouvé
});

Voir aussi

Dépannage

Journal des modifications

Learn/ai

IA & Expérience Développeur avec Flight

Aperçu

Flight facilite le renforcement de vos projets PHP avec des outils alimentés par l'IA et des flux de travail modernes pour les développeurs. Avec des commandes intégrées pour se connecter aux fournisseurs de LLM (Large Language Model) et générer des instructions de codage IA spécifiques au projet, Flight vous aide, vous et votre équipe, à tirer le meilleur parti des assistants IA comme GitHub Copilot, Cursor et Windsurf.

Compréhension

Les assistants de codage IA sont les plus utiles lorsqu'ils comprennent le contexte, les conventions et les objectifs de votre projet. Les aides IA de Flight vous permettent de :

Ces fonctionnalités sont intégrées au CLI principal de Flight et au projet de démarrage officiel flightphp/skeleton.

Utilisation de Base

Configuration des Identifiants LLM

La commande ai:init vous guide à travers la connexion de votre projet à un fournisseur de LLM.

php runway ai:init

Vous serez invité à :

Cela crée un fichier .runway-creds.json à la racine de votre projet (et s'assure qu'il est dans votre .gitignore).

Exemple :

Welcome to AI Init!
Which LLM API do you want to use? [1] openai, [2] grok, [3] claude: 1
Enter the base URL for the LLM API [https://api.openai.com]:
Enter your API key for openai: sk-...
Enter the model name you want to use (e.g. gpt-4, claude-3-opus, etc) [gpt-4o]:
Credentials saved to .runway-creds.json

Génération d'Instructions IA Spécifiques au Projet

La commande ai:generate-instructions vous aide à créer ou mettre à jour des instructions pour les assistants de codage IA, adaptées à votre projet.

php runway ai:generate-instructions

Vous répondrez à quelques questions sur votre projet (description, base de données, templating, sécurité, taille de l'équipe, etc.). Flight utilise votre fournisseur de LLM pour générer les instructions, puis les écrit dans :

Exemple :

Please describe what your project is for? My awesome API
What database are you planning on using? MySQL
What HTML templating engine will you plan on using (if any)? latte
Is security an important element of this project? (y/n) y
...
AI instructions updated successfully.

Maintenant, vos outils IA fourniront des suggestions plus intelligentes et plus pertinentes basées sur les besoins réels de votre projet.

Utilisation Avancée

Voir Aussi

Dépannage

Journal des Modifications

Learn/unit_testing_and_solid_principles

Cet article a été publié à l'origine sur Airpair en 2015. Tout le crédit revient à Airpair et à Brian Fenton, qui a rédigé cet article à l'origine, bien que le site web ne soit plus disponible et que l'article n'existe plus que dans la Wayback Machine. Cet article a été ajouté au site à des fins d'apprentissage et d'éducation pour la communauté PHP dans son ensemble.

1 Configuration et installation

1.1 Garder à jour

Disons-le dès le début : un nombre déprimant d'installations PHP en production ne sont pas à jour ou ne le restent pas. Que ce soit dû à des restrictions d'hébergement partagé, à des paramètres par défaut que personne ne modifie, ou à un manque de temps/budget pour les tests de mise à niveau, les binaires PHP ont tendance à être laissés de côté. Une pratique exemplaire claire qui mérite plus d'attention est d'utiliser toujours une version actuelle de PHP (5.6.x au moment de cet article). De plus, il est important de planifier des mises à niveau régulières de PHP lui-même ainsi que de toute extension ou bibliothèque de fournisseurs que vous utilisez. Les mises à niveau vous apportent de nouvelles fonctionnalités de langage, une vitesse améliorée, une utilisation de mémoire réduite et des mises à jour de sécurité. Plus vous mettez à niveau fréquemment, moins le processus est douloureux.

1.2 Définir des paramètres par défaut sensés

PHP fait un travail décent pour définir de bons paramètres par défaut avec ses fichiers php.ini.development et php.ini.production, mais nous pouvons faire mieux. Par exemple, ils ne définissent pas de fuseau horaire pour nous. Cela a du sens du point de vue de la distribution, mais sans cela, PHP générera une erreur E_WARNING chaque fois que nous appelons une fonction liée à la date/heure. Voici quelques paramètres recommandés :

1.3 Extensions

Il est également une bonne idée de désactiver (ou au moins de ne pas activer) les extensions que vous n'utiliserez pas, comme les pilotes de base de données. Pour voir ce qui est activé, exécutez la commande phpinfo() ou allez dans une ligne de commande et exécutez ceci.

$ php -i

Les informations sont les mêmes, mais phpinfo() ajoute un formatage HTML. La version CLI est plus facile à rediriger vers grep pour trouver des informations spécifiques. Ex.

$ php -i | grep error_log

Une mise en garde de cette méthode : il est possible d'avoir des paramètres PHP différents s'appliquant à la version orientée web et à la version CLI.

2 Utiliser Composer

Cela peut surprendre, mais l'une des meilleures pratiques pour écrire du PHP moderne est d'en écrire moins. Bien que ce soit vrai que l'un des meilleurs moyens de bien programmer est de le faire, il y a un grand nombre de problèmes qui ont déjà été résolus dans l'espace PHP, comme le routage, les bibliothèques de validation d'entrée de base, la conversion d'unités, les couches d'abstraction de base de données, etc... Allez simplement sur Packagist et explorez. Vous constaterez probablement que des parties significatives du problème que vous essayez de résoudre ont déjà été écrites et testées.

Bien qu'il soit tentant d'écrire tout le code vous-même (et il n'y a rien de mal à écrire votre propre framework ou bibliothèque comme une expérience d'apprentissage), vous devriez lutter contre ces sentiments de "Pas Inventé Ici" et vous épargner beaucoup de temps et de maux de tête. Suivez plutôt la doctrine de PIE - Fierement Inventé Ailleurs. De plus, si vous choisissez d'écrire votre propre élément, ne le publiez pas à moins qu'il ne fasse quelque chose de significativement différent ou meilleur que les offres existantes.

Composer est un gestionnaire de paquets pour PHP, similaire à pip en Python, gem en Ruby et npm en Node. Il vous permet de définir un fichier JSON qui liste les dépendances de votre code, et il essaiera de résoudre ces exigences en téléchargeant et en installant les paquets de code nécessaires.

2.1 Installer Composer

Nous supposons que c'est un projet local, donc installons une instance de Composer juste pour le projet actuel. Naviguez vers votre répertoire de projet et exécutez ceci :

$ curl -sS https://getcomposer.org/installer | php

Gardez à l'esprit que rediriger n'importe quel téléchargement directement vers un interpréteur de script (sh, ruby, php, etc.) est un risque de sécurité, donc lisez le code d'installation et assurez-vous d'être à l'aise avec cela avant d'exécuter une commande comme celle-ci.

Pour des raisons de commodité (si vous préférez taper composer install plutôt que php composer.phar install), vous pouvez utiliser cette commande pour installer une copie unique de composer globalement :

$ mv composer.phar /usr/local/bin/composer
$ chmod +x composer

Vous devrez peut-être exécuter celles-ci avec sudo en fonction de vos permissions de fichier.

2.2 Utiliser Composer

Composer a deux catégories principales de dépendances qu'il peut gérer : "require" et "require-dev". Les dépendances listées comme "require" sont installées partout, mais les dépendances "require-dev" ne sont installées que lorsqu'elles sont spécifiquement demandées. Celles-ci sont généralement des outils pour lorsque le code est en développement actif, tels que PHP_CodeSniffer. La ligne ci-dessous montre un exemple de comment installer Guzzle, une bibliothèque HTTP populaire.

$ php composer.phar require guzzle/guzzle

Pour installer un outil juste à des fins de développement, ajoutez le drapeau --dev :

$ php composer.phar require --dev 'sebastian/phpcpd'

Cela installe PHP Copy-Paste Detector, un autre outil de qualité de code en tant que dépendance de développement seulement.

2.3 Installer vs mettre à jour

Lorsque nous exécutons composer install pour la première fois, il installera les bibliothèques et leurs dépendances dont nous avons besoin, en fonction du fichier composer.json. Une fois cela fait, composer crée un fichier de verrouillage, appelé composer.lock. Ce fichier contient une liste des dépendances que composer a trouvées pour nous et leurs versions exactes, avec des hachages. Ensuite, toute fois future que nous exécutons composer install, il regardera dans le fichier de verrouillage et installera ces versions exactes.

composer update est un peu différent. Il ignorera le fichier composer.lock (s'il est présent) et essaiera de trouver les versions les plus récentes de chacune des dépendances qui satisfont encore les contraintes dans composer.json. Il écrira ensuite un nouveau fichier composer.lock une fois terminé.

2.4 Autoload

À la fois composer install et composer update généreront un autoload pour nous qui indique à PHP où trouver tous les fichiers nécessaires pour utiliser les bibliothèques que nous venons d'installer. Pour l'utiliser, ajoutez simplement cette ligne (généralement à un fichier de bootstrap qui s'exécute à chaque requête) :

require 'vendor/autoload.php';

3 Suivre les bons principes de conception

3.1 SOLID

SOLID est un mnémonique pour nous rappeler cinq principes clés dans une bonne conception de logiciel orienté objet.

3.1.1 S - Principe de responsabilité unique

Cela stipule que les classes ne devraient avoir qu'une seule responsabilité, ou dit autrement, elles ne devraient avoir qu'une seule raison de changer. Cela correspond bien à la philosophie Unix de nombreux petits outils, en faisant une chose bien. Les classes qui ne font qu'une chose sont beaucoup plus faciles à tester et à déboguer, et elles sont moins susceptibles de vous surprendre. Vous ne voulez pas qu'un appel de méthode à une classe Validator mette à jour des enregistrements de base de données. Voici un exemple d'une violation de SRP, comme on en voit couramment dans une application basée sur le modèle ActiveRecord.

class Person extends Model
{
    public $name;
    public $birthDate;
    protected $preferences;
    public function getPreferences() {}
    public function save() {}
}

Donc c'est un modèle d'entité assez basique. L'une de ces choses n'appartient pas ici cependant. La seule responsabilité d'un modèle d'entité devrait être le comportement lié à l'entité qu'il représente, il ne devrait pas être responsable de sa propre persistance.

class Person extends Model
{
    public $name;
    public $birthDate;
    protected $preferences;
    public function getPreferences() {}
}
class DataStore
{
    public function save(Model $model) {}
}

Ceci est meilleur. Le modèle Person est de retour à une seule chose, et le comportement de sauvegarde a été déplacé vers un objet de persistance. Notez également que j'ai seulement indiqué le type sur Model, pas sur Person. Nous y reviendrons lorsque nous arriverons aux parties L et D de SOLID.

3.1.2 O - Principe ouvert-fermé

Il y a un test génial pour cela qui résume assez bien ce principe : pensez à une fonctionnalité à implémenter, probablement la plus récente sur laquelle vous avez travaillé ou que vous travaillez. Pouvez-vous implémenter cette fonctionnalité dans votre base de code existante UNIQUEMENT en ajoutant de nouvelles classes et sans modifier aucune classe existante dans votre système ? Votre code de configuration et de câblage obtient un peu de passe, mais dans la plupart des systèmes, cela est étonnamment difficile. Vous devez vous appuyer beaucoup sur le dispatch polymorphe et la plupart des bases de code ne sont pas configurées pour cela. Si vous êtes intéressé, il y a une bonne conférence Google sur YouTube à propos du polymorphisme et de l'écriture de code sans Ifs qui approfondit le sujet. En bonus, la conférence est donnée par Miško Hevery, que beaucoup connaissent comme le créateur de AngularJs.

3.1.3 L - Principe de substitution de Liskov

Ce principe est nommé d'après Barbara Liskov, et est imprimé ci-dessous :

"Les objets dans un programme devraient être remplaçables par des instances de leurs sous-types sans altérer la correction de ce programme."

Cela a l'air bien et tout, mais c'est plus clairement illustré avec un exemple.

abstract class Shape
{
    public function getHeight();
    public function setHeight($height);
    public function getLength();
    public function setLength($length);
}

Ceci va représenter notre forme basique à quatre côtés. Rien de fantaisiste ici.

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

Voici notre première forme, le Carré. Une forme assez directe, non ? Vous pouvez supposer qu'il y a un constructeur où nous définissons les dimensions, mais vous voyez ici de cette implémentation que la longueur et la hauteur seront toujours les mêmes. Les carrés sont comme ça.

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

Donc ici nous avons une forme différente. Elle a toujours les mêmes signatures de méthodes, c'est toujours une forme à quatre côtés, mais si nous commençons à essayer de les utiliser à la place les unes des autres ? Maintenant, tout d'un coup, si nous changeons la hauteur de notre Shape, nous ne pouvons plus supposer que la longueur de notre forme correspondra. Nous avons violé le contrat que nous avions avec l'utilisateur lorsque nous lui avons donné notre forme Carré.

Ceci est un exemple classique d'une violation du LSP et nous avons besoin de ce type de principe pour tirer le meilleur parti d'un système de types. Même le typage duck ne nous dira pas si le comportement sous-jacent est différent, et comme nous ne pouvons pas le savoir sans le voir se casser, il est préférable de s'assurer qu'il ne l'est pas au départ.

3.1.3 I - Principe de ségrégation d'interface

Ce principe dit de favoriser de nombreuses interfaces petites et granulaires par rapport à une grande. Les interfaces devraient être basées sur le comportement plutôt que sur "c'est l'une de ces classes". Pensez aux interfaces qui viennent avec PHP. Traversable, Countable, Serializable, des choses comme ça. Elles annoncent les capacités que l'objet possède, pas ce qu'il hérite. Donc gardez vos interfaces petites. Vous ne voulez pas qu'une interface ait 30 méthodes, 3 est un objectif bien meilleur.

3.1.4 D - Principe d'inversion des dépendances

Vous avez probablement entendu parler de cela dans d'autres endroits qui parlaient de l'injection de dépendances, mais l'inversion des dépendances et l'injection de dépendances ne sont pas tout à fait la même chose. L'inversion des dépendances est vraiment juste un moyen de dire que vous devriez dépendre des abstractions dans votre système et non de ses détails. Que signifie cela pour vous au quotidien ?

N'utilisez pas directement mysqli_query() partout dans votre code, utilisez quelque chose comme DataStore->query() à la place.

Le cœur de ce principe est en fait sur les abstractions. Il s'agit plus de dire "utilisez un adaptateur de base de données" au lieu de dépendre d'appels directs à des choses comme mysqli_query. Si vous utilisez directement mysqli_query dans la moitié de vos classes, vous liez tout directement à votre base de données. Rien pour ou contre MySQL ici, mais si vous utilisez mysqli_query, ce type de détail de bas niveau devrait être caché dans un seul endroit et cette fonctionnalité devrait ensuite être exposée via un wrapper générique.

Maintenant je sais que c'est un exemple un peu usé si vous y pensez, car le nombre de fois où vous allez complètement changer votre moteur de base de données après que votre produit soit en production est très, très faible. Je l'ai choisi parce que je pensais que les gens seraient familiers avec l'idée de leur propre code. De plus, même si vous avez une base de données à laquelle vous restez fidèle, cet objet wrapper abstrait vous permet de corriger les bogues, de changer le comportement ou d'implémenter des fonctionnalités que vous souhaitez que votre base de données choisie ait. Il rend également les tests unitaires possibles là où les appels de bas niveau ne le feraient pas.

4 Exercices d'objets

Ceci n'est pas un plongeon complet dans ces principes, mais les deux premiers sont faciles à retenir, apportent une bonne valeur et peuvent être appliqués immédiatement à presque n'importe quelle base de code.

4.1 Pas plus d'un niveau d'indentation par méthode

Ceci est un moyen utile de penser à décomposer les méthodes en morceaux plus petits, laissant un code plus clair et plus auto-documenté. Plus vous avez de niveaux d'indentation, plus la méthode fait de choses et plus d'état vous devez suivre dans votre tête pendant que vous travaillez avec.

Tout de suite, je sais que les gens vont s'opposer à cela, mais ceci n'est qu'une ligne directrice/règle heuristique, pas une règle dure et rapide. Je ne m'attends pas à ce que quiconque fasse respecter les règles PHP_CodeSniffer pour cela (bien que des gens l'aient fait).

Parcourons rapidement un échantillon de ce à quoi cela pourrait ressembler :

public function transformToCsv($data)
{
    $csvLines = array();
    $csvLines[] = implode(',', array_keys($data[0]));
    foreach ($data as $row) {
        if (!$row) {
            continue;
        }
        $csvLines[] = implode(',', $row);
    }
    return $csvLines;
}

Bien que ce ne soit pas un code terrible (il est techniquement correct, testable, etc.), nous pouvons faire beaucoup plus pour le rendre clair. Comment réduirions-nous les niveaux d'imbrication ici ?

Nous savons que nous devons simplifier énormément le contenu de la boucle foreach (ou l'enlever complètement), donc commençons par là.

if (!$row) {
    continue;
}

Cette première partie est facile. Tout ce qu'elle fait, c'est ignorer les lignes vides. Nous pouvons contourner cela en utilisant une fonction PHP intégrée avant même d'arriver à la boucle.

$data = array_filter($data);
foreach ($data as $row) {
    $csvLines[] = implode(',', $row);
}

Nous avons maintenant notre seul niveau d'imbrication. Mais en regardant cela, tout ce que nous faisons, c'est appliquer une fonction à chaque élément d'un tableau. Nous n'avons même pas besoin de la boucle foreach pour cela.

$data = array_filter($data);
$csvLines = array_map(function($row) {
    return implode(',', $row);
}, $data);

Maintenant nous n'avons plus d'imbrication du tout, et le code sera probablement plus rapide car nous faisons toute la boucle avec des fonctions C natives au lieu de PHP. Nous devons cependant nous livrer à un peu de tricherie pour passer la virgule à implode, donc on pourrait argumenter que s'arrêter à l'étape précédente est beaucoup plus compréhensible.

4.2 Essayer de ne pas utiliser else

Ceci traite vraiment de deux idées principales. La première est les déclarations de retour multiples d'une méthode. Si vous avez assez d'informations pour prendre une décision sur le résultat de la méthode, allez-y et prenez cette décision et retournez. La seconde est une idée connue sous le nom de clauses de garde. Celles-ci sont essentiellement des vérifications de validation combinées à des retours précoces, généralement près du sommet d'une méthode. Laissez-moi vous montrer ce que je veux dire.

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

Donc c'est assez direct, cela ajoute 3 entiers ensemble et retourne le résultat, ou null si l'un des paramètres n'est pas un entier. En ignorant le fait que nous pourrions combiner toutes ces vérifications sur une seule ligne avec des opérateurs ET, je pense que vous pouvez voir comment la structure if/else imbriquée rend le code plus difficile à suivre. Maintenant regardez cet exemple à la place.

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

Pour moi, cet exemple est beaucoup plus facile à suivre. Ici, nous utilisons des clauses de garde pour vérifier nos affirmations initiales sur les paramètres que nous passons et quittons immédiatement la méthode s'ils ne passent pas. Nous n'avons plus non plus la variable intermédiaire pour suivre la somme tout au long de la méthode. Dans ce cas, nous avons vérifié que nous sommes déjà sur le chemin heureux et nous pouvons simplement faire ce que nous sommes venus faire. Encore une fois, nous pourrions faire toutes ces vérifications dans un seul if, mais le principe devrait être clair.

5 Tests unitaires

Les tests unitaires sont la pratique d'écriture de petits tests qui vérifient le comportement dans votre code. Ils sont presque toujours écrits dans le même langage que le code (dans ce cas PHP) et sont destinés à être assez rapides pour s'exécuter à tout moment. Ils sont extrêmement précieux en tant qu'outil pour améliorer votre code. En plus des avantages évidents de s'assurer que votre code fait ce que vous pensez qu'il fait, les tests unitaires peuvent fournir un retour de conception très utile. Si un morceau de code est difficile à tester, il souligne souvent des problèmes de conception. Ils vous donnent également un filet de sécurité contre les régressions, et cela vous permet de refactoriser beaucoup plus souvent et d'évoluer vers une conception plus propre.

5.1 Outils

Il y a plusieurs outils de tests unitaires dans PHP, mais de loin le plus courant est PHPUnit. Vous pouvez l'installer en téléchargeant un fichier PHAR directement, ou en l'installant avec composer. Comme nous utilisons composer pour tout le reste, nous montrerons cette méthode. De plus, comme PHPUnit n'est probablement pas destiné à être déployé en production, nous pouvons l'installer en tant que dépendance de développement avec la commande suivante :

composer require --dev phpunit/phpunit

5.2 Les tests sont une spécification

Le rôle le plus important des tests unitaires dans votre code est de fournir une spécification exécutable de ce que le code est censé faire. Même si le code de test est erroné, ou si le code a des bogues, la connaissance de ce que le système est censé faire est inestimable.

5.3 Écrivez vos tests en premier

Si vous avez eu la chance de voir un ensemble de tests écrits avant le code et un écrit après que le code soit terminé, ils sont étonnamment différents. Les tests "après" sont beaucoup plus préoccupés par les détails d'implémentation de la classe et s'assurer qu'ils ont une bonne couverture de lignes, alors que les tests "avant" sont plus sur la vérification du comportement externe souhaité. C'est vraiment ce qui nous intéresse avec les tests unitaires de toute façon, c'est de s'assurer que la classe exhibe le bon comportement. Les tests axés sur l'implémentation rendent en fait le refactoring plus difficile car ils se cassent si les internes des classes changent, et vous venez de vous coûter les avantages de la dissimulation d'informations de la POO.

5.4 Ce qui fait un bon test unitaire

Les bons tests unitaires partagent beaucoup des caractéristiques suivantes :

Il y a des raisons d'aller à l'encontre de certaines de celles-ci, mais en tant que lignes directrices générales, elles vous serviront bien.

5.5 Quand les tests sont douloureux

Les tests unitaires vous forcent à ressentir la douleur d'une mauvaise conception dès le début - Michael Feathers

Lorsque vous écrivez des tests unitaires, vous vous forcez à utiliser réellement la classe pour accomplir des choses. Si vous écrivez des tests à la fin, ou pire encore, si vous jetez simplement le code par-dessus le mur pour que QA ou qui que ce soit écrive des tests, vous ne recevez aucun retour sur la façon dont la classe se comporte réellement. Si nous écrivons des tests, et que la classe est une vraie douleur à utiliser, nous le découvrirons pendant que nous l'écrivons, ce qui est presque le moment le moins cher pour le corriger.

Si une classe est difficile à tester, c'est un défaut de conception. Différents défauts se manifestent de différentes manières cependant. Si vous devez faire beaucoup de moqueries, votre classe a probablement trop de dépendances, ou vos méthodes font trop. Plus vous avez de configuration pour chaque test, plus il est probable que vos méthodes font trop. Si vous devez écrire des scénarios de test vraiment compliqués pour exercer un comportement, les méthodes de la classe font probablement trop. Si vous devez creuser à l'intérieur d'une foule de méthodes privées et d'état pour tester des choses, peut-être qu'une autre classe essaie de sortir. Les tests unitaires sont très bons pour exposer les "classes iceberg" où 80% de ce que la classe fait est caché dans du code protégé ou privé. J'étais autrefois un grand fan de rendre autant que possible protégé, mais maintenant j'ai réalisé que je rendais simplement mes classes individuelles responsables de trop, et la vraie solution était de diviser la classe en morceaux plus petits.

Écrit par Brian Fenton - Brian Fenton est un développeur PHP depuis 8 ans dans le Midwest et la Bay Area, actuellement chez Thismoment. Il se concentre sur l'artisanat du code et les principes de conception. Blog sur www.brianfenton.us, Twitter sur @brianfenton. Quand il n'est pas occupé à être un père, il aime la nourriture, la bière, les jeux et l'apprentissage.

Learn/security

Sécurité

Aperçu

La sécurité est une priorité majeure pour les applications web. Vous devez vous assurer que votre application est sécurisée et que les données de vos utilisateurs sont protégées. Flight fournit un certain nombre de fonctionnalités pour vous aider à sécuriser vos applications web.

Compréhension

Il existe un certain nombre de menaces de sécurité courantes dont vous devez être conscient lors de la construction d'applications web. Parmi les menaces les plus courantes, on trouve :

Templates aident avec XSS en échappant la sortie par défaut, afin que vous n'ayez pas à vous en souvenir. Sessions peuvent aider avec CSRF en stockant un jeton CSRF dans la session de l'utilisateur comme indiqué ci-dessous. L'utilisation de requêtes préparées avec PDO peut aider à prévenir les attaques par injection SQL (ou en utilisant les méthodes pratiques dans la classe PdoWrapper). CORS peut être géré avec un simple hook avant que Flight::start() ne soit appelé.

Toutes ces méthodes fonctionnent ensemble pour aider à garder vos applications web sécurisées. Il devrait toujours être à l'avant-plan de votre esprit d'apprendre et de comprendre les meilleures pratiques de sécurité.

Utilisation de base

En-têtes

Les en-têtes HTTP sont l'un des moyens les plus simples de sécuriser vos applications web. Vous pouvez utiliser des en-têtes pour prévenir le clickjacking, XSS et d'autres attaques. Il existe plusieurs façons d'ajouter ces en-têtes à votre application.

Deux excellents sites web pour vérifier la sécurité de vos en-têtes sont securityheaders.com et observatory.mozilla.org. Après avoir configuré le code ci-dessous, vous pouvez facilement vérifier que vos en-têtes fonctionnent avec ces deux sites.

Ajout manuel

Vous pouvez ajouter manuellement ces en-têtes en utilisant la méthode header sur l'objet Flight\Response.

// Définir l'en-tête X-Frame-Options pour prévenir le clickjacking
Flight::response()->header('X-Frame-Options', 'SAMEORIGIN');

// Définir l'en-tête Content-Security-Policy pour prévenir XSS
// Note : cet en-tête peut devenir très complexe, vous devrez donc
//  consulter des exemples sur internet pour votre application
Flight::response()->header("Content-Security-Policy", "default-src 'self'");

// Définir l'en-tête X-XSS-Protection pour prévenir XSS
Flight::response()->header('X-XSS-Protection', '1; mode=block');

// Définir l'en-tête X-Content-Type-Options pour prévenir le sniffing MIME
Flight::response()->header('X-Content-Type-Options', 'nosniff');

// Définir l'en-tête Referrer-Policy pour contrôler la quantité d'informations de référent envoyées
Flight::response()->header('Referrer-Policy', 'no-referrer-when-downgrade');

// Définir l'en-tête Strict-Transport-Security pour forcer HTTPS
Flight::response()->header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');

// Définir l'en-tête Permissions-Policy pour contrôler les fonctionnalités et API utilisables
Flight::response()->header('Permissions-Policy', 'geolocation=()');

Ces en-têtes peuvent être ajoutés en haut de vos fichiers routes.php ou index.php.

Ajout en tant que filtre

Vous pouvez également les ajouter dans un filtre/hook comme suit :

// Ajouter les en-têtes dans un filtre
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=()');
});

Ajout en tant que middleware

Vous pouvez également les ajouter en tant que classe middleware, ce qui offre la plus grande flexibilité pour choisir les routes auxquelles les appliquer. En général, ces en-têtes devraient être appliqués à toutes les réponses HTML et API.

// 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 ou là où vous avez vos routes
// FYI, ce groupe de chaîne vide agit comme un middleware global pour
// toutes les routes. Bien sûr, vous pourriez faire la même chose et simplement ajouter
// cela uniquement à des routes spécifiques.
Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // plus de routes
}, [ SecurityHeadersMiddleware::class ]);

Cross Site Request Forgery (CSRF)

Cross Site Request Forgery (CSRF) est un type d'attaque où un site web malveillant peut faire en sorte que le navigateur d'un utilisateur envoie une requête à votre site web. Cela peut être utilisé pour effectuer des actions sur votre site web sans la connaissance de l'utilisateur. Flight ne fournit pas de mécanisme de protection CSRF intégré, mais vous pouvez facilement implémenter le vôtre en utilisant un middleware.

Configuration

D'abord, vous devez générer un jeton CSRF et le stocker dans la session de l'utilisateur. Vous pouvez ensuite utiliser ce jeton dans vos formulaires et le vérifier quand le formulaire est soumis. Nous utiliserons le plugin flightphp/session pour gérer les sessions.

// Générer un jeton CSRF et le stocker dans la session de l'utilisateur
// (en supposant que vous ayez créé un objet session et l'avez attaché à Flight)
// voir la documentation des sessions pour plus d'informations
Flight::register('session', flight\Session::class);

// Vous n'avez besoin de générer qu'un seul jeton par session (afin qu'il fonctionne 
// sur plusieurs onglets et requêtes pour le même utilisateur)
if(Flight::session()->get('csrf_token') === null) {
    Flight::session()->set('csrf_token', bin2hex(random_bytes(32)) );
}
Utilisation du template Flight PHP par défaut
<!-- Utiliser le jeton CSRF dans votre formulaire -->
<form method="post">
    <input type="hidden" name="csrf_token" value="<?= Flight::session()->get('csrf_token') ?>">
    <!-- autres champs du formulaire -->
</form>
Utilisation de Latte

Vous pouvez également définir une fonction personnalisée pour afficher le jeton CSRF dans vos templates Latte.


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

    // autres configurations...

    // Définir une fonction personnalisée pour afficher le jeton CSRF
    $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);
});

Et maintenant dans vos templates Latte, vous pouvez utiliser la fonction csrf() pour afficher le jeton CSRF.

<form method="post">
    {csrf()}
    <!-- autres champs du formulaire -->
</form>

Vérifier le jeton CSRF

Vous pouvez vérifier le jeton CSRF en utilisant plusieurs méthodes.

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, 'Jeton CSRF invalide');
            }
        }
    }
}

// index.php ou là où vous avez vos routes
use app\middlewares\CsrfMiddleware;

Flight::group('', function(Router $router) {
    $router->get('/users', [ 'UserController', 'getUsers' ]);
    // plus de routes
}, [ CsrfMiddleware::class ]);
Filtres d'événements
// Ce middleware vérifie si la requête est une requête POST et si c'est le cas, il vérifie si le jeton CSRF est valide
Flight::before('start', function() {
    if(Flight::request()->method == 'POST') {

        // capturer le jeton csrf des valeurs du formulaire
        $token = Flight::request()->data->csrf_token;
        if($token !== Flight::session()->get('csrf_token')) {
            Flight::halt(403, 'Jeton CSRF invalide');
            // ou pour une réponse JSON
            Flight::jsonHalt(['error' => 'Jeton CSRF invalide'], 403);
        }
    }
});

Cross Site Scripting (XSS)

Cross Site Scripting (XSS) est un type d'attaque où une entrée de formulaire malveillante peut injecter du code dans votre site web. La plupart de ces opportunités proviennent des valeurs de formulaire que vos utilisateurs finaux rempliront. Vous ne devez jamais faire confiance à la sortie de vos utilisateurs ! Supposez toujours qu'ils sont les meilleurs hackers au monde. Ils peuvent injecter du JavaScript ou du HTML malveillant dans votre page. Ce code peut être utilisé pour voler des informations à vos utilisateurs ou effectuer des actions sur votre site web. En utilisant la classe view de Flight ou un autre moteur de templating comme Latte, vous pouvez facilement échapper la sortie pour prévenir les attaques XSS.

// Supposons que l'utilisateur soit astucieux et essaie d'utiliser ceci comme nom
$name = '<script>alert("XSS")</script>';

// Cela échappera la sortie
Flight::view()->set('name', $name);
// Cela affichera : &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Si vous utilisez quelque chose comme Latte enregistré comme votre classe view, il échappera automatiquement cela.
Flight::view()->render('template', ['name' => $name]);

SQL Injection

SQL Injection est un type d'attaque où un utilisateur malveillant peut injecter du code SQL dans votre base de données. Cela peut être utilisé pour voler des informations de votre base de données ou effectuer des actions sur votre base de données. Encore une fois, vous ne devez jamais faire confiance à l'entrée de vos utilisateurs ! Supposez toujours qu'ils en ont après votre peau. L'utilisation de requêtes préparées dans vos objets PDO préviendra les injections SQL.

// En supposant que vous ayez Flight::db() enregistré comme votre objet PDO
$statement = Flight::db()->prepare('SELECT * FROM users WHERE username = :username');
$statement->execute([':username' => $username]);
$users = $statement->fetchAll();

// Si vous utilisez la classe PdoWrapper, cela peut être fait facilement en une ligne
$users = Flight::db()->fetchAll('SELECT * FROM users WHERE username = :username', [ 'username' => $username ]);

// Vous pouvez faire la même chose avec un objet PDO avec des placeholders ?
$statement = Flight::db()->fetchAll('SELECT * FROM users WHERE username = ?', [ $username ]);

Exemple non sécurisé

Voici pourquoi nous utilisons des requêtes préparées SQL pour nous protéger d'exemples innocents comme celui-ci :

// l'utilisateur final remplit un formulaire web.
// pour la valeur du formulaire, le hacker met quelque chose comme ceci :
$username = "' OR 1=1; -- ";

$sql = "SELECT * FROM users WHERE username = '$username' LIMIT 5";
$users = Flight::db()->fetchAll($sql);
// Après que la requête soit construite, cela ressemble à ceci
// SELECT * FROM users WHERE username = '' OR 1=1; -- LIMIT 5

// Cela semble étrange, mais c'est une requête valide qui fonctionnera. En fait,
// c'est une attaque d'injection SQL très courante qui renverra tous les utilisateurs.

var_dump($users); // cela dumpera tous les utilisateurs dans la base de données, pas seulement le nom d'utilisateur unique

CORS

Cross-Origin Resource Sharing (CORS) est un mécanisme qui permet à de nombreuses ressources (par exemple, polices, JavaScript, etc.) sur une page web d'être requises depuis un autre domaine en dehors du domaine d'origine de la ressource. Flight n'a pas de fonctionnalité intégrée, mais cela peut facilement être géré avec un hook à exécuter avant que la méthode Flight::start() ne soit appelée.

// 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
    {
        // personnalisez vos hôtes autorisés ici.
        $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 ou là où vous avez vos routes
$CorsUtil = new CorsUtil();

// Cela doit être exécuté avant que start ne s'exécute.
Flight::before('start', [ $CorsUtil, 'setupCors' ]);

Gestion des erreurs

Masquez les détails d'erreurs sensibles en production pour éviter de divulguer des informations aux attaquants. En production, enregistrez les erreurs au lieu de les afficher avec display_errors défini à 0.

// Dans votre bootstrap.php ou index.php

// ajoutez ceci à votre app/config/config.php
$environment = ENVIRONMENT;
if ($environment === 'production') {
    ini_set('display_errors', 0); // Désactiver l'affichage des erreurs
    ini_set('log_errors', 1);     // Enregistrer les erreurs à la place
    ini_set('error_log', '/path/to/error.log');
}

// Dans vos routes ou contrôleurs
// Utilisez Flight::halt() pour des réponses d'erreur contrôlées
Flight::halt(403, 'Accès refusé');

Assainissement des entrées

Ne faites jamais confiance à l'entrée utilisateur. Assainissez-la en utilisant filter_var avant de la traiter pour empêcher les données malveillantes de s'infiltrer.


// Supposons une requête $_POST avec $_POST['input'] et $_POST['email']

// Assainir une entrée chaîne
$clean_input = filter_var(Flight::request()->data->input, FILTER_SANITIZE_STRING);
// Assainir un email
$clean_email = filter_var(Flight::request()->data->email, FILTER_SANITIZE_EMAIL);

Hachage des mots de passe

Stockez les mots de passe de manière sécurisée et vérifiez-les en toute sécurité en utilisant les fonctions intégrées de PHP comme password_hash et password_verify. Les mots de passe ne doivent jamais être stockés en texte clair, ni chiffrés avec des méthodes réversibles. Le hachage garantit que même si votre base de données est compromise, les mots de passe réels restent protégés.

$password = Flight::request()->data->password;
// Hacher un mot de passe lors du stockage (par exemple, pendant l'inscription)
$hashed_password = password_hash($password, PASSWORD_DEFAULT);

// Vérifier un mot de passe (par exemple, pendant la connexion)
if (password_verify($password, $stored_hash)) {
    // Le mot de passe correspond
}

Limitation de taux

Protégez contre les attaques par force brute ou les attaques par déni de service en limitant les taux de requêtes avec un cache.

// En supposant que vous ayez flightphp/cache installé et enregistré
// Utilisation de flightphp/cache dans un filtre
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, 'Trop de requêtes');
    }

    $cache->set($key, $attempts + 1, 60); // Réinitialiser après 60 secondes
});

Voir aussi

Dépannage

Journal des modifications

Learn/routing

Routage

Aperçu

Le routage dans Flight PHP mappe les motifs d'URL sur des fonctions de rappel ou des méthodes de classe, permettant une gestion rapide et simple des requêtes. Il est conçu pour un overhead minimal, une utilisation conviviale pour les débutants, et une extensibilité sans dépendances externes.

Comprendre

Le routage est le mécanisme central qui connecte les requêtes HTTP à la logique de votre application dans Flight. En définissant des routes, vous spécifiez comment différentes URL déclenchent du code spécifique, que ce soit par des fonctions, des méthodes de classe ou des actions de contrôleur. Le système de routage de Flight est flexible, supportant des motifs basiques, des paramètres nommés, des expressions régulières, et des fonctionnalités avancées comme l'injection de dépendances et le routage de ressources. Cette approche garde votre code organisé et facile à maintenir, tout en restant rapide et simple pour les débutants et extensible pour les utilisateurs avancés.

Note : Vous voulez en savoir plus sur le routage ? Consultez la page "pourquoi un framework ?" pour une explication plus approfondie.

Utilisation de base

Définir une route simple

Le routage de base dans Flight se fait en associant un motif d'URL à une fonction de rappel ou à un tableau d'une classe et d'une méthode.

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

Les routes sont associées dans l'ordre où elles sont définies. La première route qui correspond à une requête sera invoquée.

Utiliser des fonctions comme rappels

Le rappel peut être n'importe quel objet qui est invocable. Vous pouvez donc utiliser une fonction régulière :

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

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

Utiliser des classes et des méthodes comme contrôleur

Vous pouvez également utiliser une méthode (statique ou non) d'une classe :

class GreetingController {
    public function hello() {
        echo 'hello world!';
    }
}

Flight::route('/', [ 'GreetingController','hello' ]);
// ou
Flight::route('/', [ GreetingController::class, 'hello' ]); // méthode préférée
// ou
Flight::route('/', [ 'GreetingController::hello' ]);
// ou 
Flight::route('/', [ 'GreetingController->hello' ]);

Ou en créant d'abord un objet puis en appelant la méthode :

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' ]);

Note : Par défaut, lorsqu'un contrôleur est appelé dans le framework, la classe flight\Engine est toujours injectée sauf si vous spécifiez via un conteneur d'injection de dépendances

Routage spécifique à la méthode

Par défaut, les motifs de route sont associés à toutes les méthodes de requête. Vous pouvez répondre à des méthodes spécifiques en plaçant un identifiant avant l'URL.

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

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

// Vous ne pouvez pas utiliser Flight::get() pour les routes car c'est une méthode 
//    pour obtenir des variables, pas pour créer une route.
Flight::post('/', function() { /* code */ });
Flight::patch('/', function() { /* code */ });
Flight::put('/', function() { /* code */ });
Flight::delete('/', function() { /* code */ });

Vous pouvez également mapper plusieurs méthodes à un seul rappel en utilisant un délimiteur | :

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

Gestion spéciale pour les requêtes HEAD et OPTIONS

Flight fournit une gestion intégrée pour les requêtes HTTP HEAD et OPTIONS :

Requêtes HEAD

Flight::route('GET /info', function() {
    echo 'This is some info!';
});
// Une requête HEAD vers /info renverra les mêmes en-têtes, mais pas de corps.

Requêtes OPTIONS

Les requêtes OPTIONS sont automatiquement gérées par Flight pour toute route définie.

// Pour une route définie comme :
Flight::route('GET|POST /users', function() { /* ... */ });

// Une requête OPTIONS vers /users répondra avec :
//
// Status: 204 No Content
// Allow: GET, POST, HEAD, OPTIONS

Utiliser l'objet Router

De plus, vous pouvez obtenir l'objet Router qui dispose de méthodes d'aide pour votre utilisation :


$router = Flight::router();

// mappe toutes les méthodes comme Flight::route()
$router->map('/', function() {
    echo 'hello world!';
});

// requête GET
$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 */});

Expressions régulières (Regex)

Vous pouvez utiliser des expressions régulières dans vos routes :

Flight::route('/user/[0-9]+', function () {
  // Cela correspondra à /user/1234
});

Bien que cette méthode soit disponible, il est recommandé d'utiliser des paramètres nommés, ou des paramètres nommés avec des expressions régulières, car ils sont plus lisibles et plus faciles à maintenir.

Paramètres nommés

Vous pouvez spécifier des paramètres nommés dans vos routes qui seront passés à votre fonction de rappel. Ceci est plus pour la lisibilité de la route que pour toute autre chose. Veuillez voir la section ci-dessous sur l'avertissement important.

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

Vous pouvez également inclure des expressions régulières avec vos paramètres nommés en utilisant le délimiteur : :

Flight::route('/@name/@id:[0-9]{3}', function (string $name, string $id) {
  // Cela correspondra à /bob/123
  // Mais ne correspondra pas à /bob/12345
});

Note : La correspondance de groupes regex () avec des paramètres positionnels n'est pas supportée. Ex : :'\(

Avertissement important

Bien que dans l'exemple ci-dessus, il semble que @name soit directement lié à la variable $name, ce n'est pas le cas. L'ordre des paramètres dans la fonction de rappel détermine ce qui lui est passé. Si vous inversiez l'ordre des paramètres dans la fonction de rappel, les variables seraient également inversées. Voici un exemple :

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

Et si vous alliez à l'URL suivante : /bob/123, la sortie serait hello, 123 (bob)!. Soyez prudent lorsque vous configurez vos routes et vos fonctions de rappel !

Paramètres optionnels

Vous pouvez spécifier des paramètres nommés qui sont optionnels pour la correspondance en enveloppant les segments entre parenthèses.

Flight::route(
  '/blog(/@year(/@month(/@day)))',
  function(?string $year, ?string $month, ?string $day) {
    // Cela correspondra aux URL suivantes :
    // /blog/2012/12/10
    // /blog/2012/12
    // /blog/2012
    // /blog
  }
);

Tout paramètre optionnel qui n'est pas associé sera passé en tant que NULL.

Routage avec joker

La correspondance n'est faite que sur des segments d'URL individuels. Si vous voulez associer plusieurs segments, vous pouvez utiliser le joker *.

Flight::route('/blog/*', function () {
  // Cela correspondra à /blog/2000/02/01
});

Pour router toutes les requêtes vers un seul rappel, vous pouvez faire :

Flight::route('*', function () {
  // Faites quelque chose
});

Gestionnaire 404 Non trouvé

Par défaut, si une URL ne peut pas être trouvée, Flight enverra une réponse HTTP 404 Not Found qui est très simple et basique. Si vous voulez avoir une réponse 404 plus personnalisée, vous pouvez mapper votre propre méthode notFound :

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

    // Vous pourriez aussi utiliser Flight::render() avec un modèle personnalisé.
    $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();
});

Gestionnaire Méthode non trouvée

Par défaut, si une URL est trouvée mais que la méthode n'est pas autorisée, Flight enverra une réponse HTTP 405 Method Not Allowed qui est très simple et basique (Ex : Method Not Allowed. Allowed Methods are: GET, POST). Elle inclura également un en-tête Allow avec les méthodes autorisées pour cette URL.

Si vous voulez avoir une réponse 405 plus personnalisée, vous pouvez mapper votre propre méthode methodNotFound :

use flight\net\Route;

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

    // Vous pourriez aussi utiliser Flight::render() avec un modèle personnalisé.
    $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();
});

Utilisation avancée

Injection de dépendances dans les routes

Si vous voulez utiliser l'injection de dépendances via un conteneur (PSR-11, PHP-DI, Dice, etc.), le seul type de routes où cela est disponible est soit en créant directement l'objet vous-même et en utilisant le conteneur pour créer votre objet, soit en utilisant des chaînes pour définir la classe et la méthode à appeler. Vous pouvez consulter la page Injection de dépendances pour plus d'informations.

Voici un exemple rapide :


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

// Configurez le conteneur avec les paramètres dont vous avez besoin
// Voir la page Injection de dépendances pour plus d'informations sur PSR-11
$dice = new \Dice\Dice();

// N'oubliez pas de réassigner la variable avec '$dice = '!!!!! 
$dice = $dice->addRule('flight\database\PdoWrapper', [
    'shared' => true,
    'constructParams' => [ 
        'mysql:host=localhost;dbname=test', 
        'root',
        'password'
    ]
]);

// Enregistrez le gestionnaire de conteneur
Flight::registerContainerHandler(function($class, $params) use ($dice) {
    return $dice->create($class, $params);
});

// Routes comme d'habitude
Flight::route('/hello/@id', [ 'Greeting', 'hello' ]);
// ou
Flight::route('/hello/@id', 'Greeting->hello');
// ou
Flight::route('/hello/@id', 'Greeting::hello');

Flight::start();

Passer l'exécution à la route suivante

Déprécié Vous pouvez passer l'exécution à la route correspondante suivante en retournant true depuis votre fonction de rappel.

Flight::route('/user/@name', function (string $name) {
  // Vérifiez une condition
  if ($name !== "Bob") {
    // Continuez vers la route suivante
    return true;
  }
});

Flight::route('/user/*', function () {
  // Cela sera appelé
});

Il est maintenant recommandé d'utiliser middleware pour gérer des cas d'utilisation complexes comme celui-ci.

Aliasing de route

En assignant un alias à une route, vous pouvez plus tard appeler cet alias dans votre application de manière dynamique pour qu'il soit généré plus tard dans votre code (ex : un lien dans un modèle HTML, ou générer une URL de redirection).

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

// plus tard dans le code quelque part
class UserController {
    public function update() {

        // code pour sauvegarder l'utilisateur...
        $id = $user['id']; // 5 par exemple

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

Ceci est particulièrement utile si votre URL change. Dans l'exemple ci-dessus, supposons que users ait été déplacé vers /admin/users/@id à la place. Avec l'aliasing en place pour la route, vous n'avez plus besoin de trouver toutes les anciennes URL dans votre code et de les changer car l'alias renverra maintenant /admin/users/5 comme dans l'exemple ci-dessus.

L'aliasing de route fonctionne encore dans les groupes :

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

Inspection des informations de route

Si vous voulez inspecter les informations de la route correspondante, il y a 2 façons de faire ceci :

  1. Vous pouvez utiliser une propriété executedRoute sur l'objet Flight::router().
  2. Vous pouvez demander que l'objet route soit passé à votre rappel en passant true comme troisième paramètre dans la méthode route. L'objet route sera toujours le dernier paramètre passé à votre fonction de rappel.

executedRoute

Flight::route('/', function() {
  $route = Flight::router()->executedRoute;
  // Faites quelque chose avec $route
  // Tableau des méthodes HTTP associées
  $route->methods;

  // Tableau des paramètres nommés
  $route->params;

  // Expression régulière correspondante
  $route->regex;

  // Contient le contenu de tout '*' utilisé dans le motif d'URL
  $route->splat;

  // Montre le chemin d'URL....si vous en avez vraiment besoin
  $route->pattern;

  // Montre le middleware assigné à ceci
  $route->middleware;

  // Montre l'alias assigné à cette route
  $route->alias;
});

Note : La propriété executedRoute ne sera définie qu'après qu'une route ait été exécutée. Si vous essayez d'y accéder avant qu'une route ait été exécutée, elle sera NULL. Vous pouvez aussi utiliser executedRoute dans middleware !

Passer true à la définition de route

Flight::route('/', function(\flight\net\Route $route) {
  // Tableau des méthodes HTTP associées
  $route->methods;

  // Tableau des paramètres nommés
  $route->params;

  // Expression régulière correspondante
  $route->regex;

  // Contient le contenu de tout '*' utilisé dans le motif d'URL
  $route->splat;

  // Montre le chemin d'URL....si vous en avez vraiment besoin
  $route->pattern;

  // Montre le middleware assigné à ceci
  $route->middleware;

  // Montre l'alias assigné à cette route
  $route->alias;
}, true);// <-- Ce paramètre true est ce qui rend cela possible

Groupement de routes et Middleware

Il peut y avoir des moments où vous voulez grouper des routes liées ensemble (comme /api/v1). Vous pouvez le faire en utilisant la méthode group :

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

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

Vous pouvez même imbriquer des groupes de groupes :

Flight::group('/api', function () {
  Flight::group('/v1', function () {
    // Flight::get() obtient des variables, cela ne définit pas une route ! Voir le contexte objet ci-dessous
    Flight::route('GET /users', function () {
      // Correspond à GET /api/v1/users
    });

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

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

    // Flight::get() obtient des variables, cela ne définit pas une route ! Voir le contexte objet ci-dessous
    Flight::route('GET /users', function () {
      // Correspond à GET /api/v2/users
    });
  });
});

Groupement avec contexte objet

Vous pouvez toujours utiliser le groupement de routes avec l'objet Engine de la manière suivante :

$app = Flight::app();

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

  // utilisez la variable $router
  $router->get('/users', function () {
    // Correspond à GET /api/v1/users
  });

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

Note : C'est la méthode préférée pour définir des routes et des groupes avec l'objet $router.

Groupement avec Middleware

Vous pouvez également assigner un middleware à un groupe de routes :

Flight::group('/api/v1', function () {
  Flight::route('/users', function () {
    // Correspond à /api/v1/users
  });
}, [ MyAuthMiddleware::class ]); // ou [ new MyAuthMiddleware() ] si vous voulez utiliser une instance

Voir plus de détails sur la page group middleware.

Routage de ressources

Vous pouvez créer un ensemble de routes pour une ressource en utilisant la méthode resource. Cela créera un ensemble de routes pour une ressource qui suit les conventions RESTful.

Pour créer une ressource, faites ceci :

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

Et ce qui se passera en arrière-plan est qu'il créera les routes suivantes :

[
      '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'
]

Et votre contrôleur utilisera les méthodes suivantes :

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

Note : Vous pouvez visualiser les routes nouvellement ajoutées avec runway en exécutant php runway routes.

Personnalisation des routes de ressources

Il y a quelques options pour configurer les routes de ressources.

Alias de base

Vous pouvez configurer l'aliasBase. Par défaut, l'alias est la dernière partie de l'URL spécifiée. Par exemple, /users/ résulterait en un aliasBase de users. Lorsque ces routes sont créées, les alias sont users.index, users.create, etc. Si vous voulez changer l'alias, définissez l'aliasBase à la valeur que vous voulez.

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

Vous pouvez également spécifier quelles routes vous voulez créer en utilisant les options only et except.

// Liste blanche seulement ces méthodes et liste noire le reste
Flight::resource('/users', UsersController::class, [ 'only' => [ 'index', 'show' ] ]);
// Liste noire seulement ces méthodes et liste blanche le reste
Flight::resource('/users', UsersController::class, [ 'except' => [ 'create', 'store', 'edit', 'update', 'destroy' ] ]);

Ce sont essentiellement des options de liste blanche et liste noire pour que vous puissiez spécifier quelles routes vous voulez créer.

Middleware

Vous pouvez également spécifier un middleware à exécuter sur chacune des routes créées par la méthode resource.

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

Réponses en streaming

Vous pouvez maintenant diffuser des réponses au client en utilisant stream() ou streamWithHeaders(). Ceci est utile pour envoyer de grands fichiers, des processus à longue durée, ou générer de grandes réponses. Le streaming d'une route est géré un peu différemment qu'une route régulière.

Note : Les réponses en streaming ne sont disponibles que si vous avez flight.v2.output_buffering défini à false.

Stream avec en-têtes manuels

Vous pouvez diffuser une réponse au client en utilisant la méthode stream() sur une route. Si vous faites cela, vous devez définir tous les en-têtes manuellement avant de sortir quoi que ce soit vers le client. Ceci se fait avec la fonction php header() ou la méthode Flight::response()->setRealHeader().

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

    $response = Flight::response();

    // évidemment vous devriez sanitiser le chemin et tout ça.
    $fileNameSafe = basename($filename);

    // Si vous avez des en-têtes supplémentaires à définir ici après que la route ait été exécutée
    // vous devez les définir avant que quoi que ce soit ne soit échoé.
    // Ils doivent tous être un appel brut à la fonction header() ou 
    // un appel à Flight::response()->setRealHeader()
    header('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');
    // ou
    $response->setRealHeader('Content-Disposition: attachment; filename="'.$fileNameSafe.'"');

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

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

    // définissez manuellement la longueur du contenu si vous le souhaitez
    header('Content-Length: '.filesize($filePath));
    // ou
    $response->setRealHeader('Content-Length: '.filesize($filePath));

    // Diffusez le fichier au client au fur et à mesure qu'il est lu
    readfile($filePath);

// C'est la ligne magique ici
})->stream();

Stream avec en-têtes

Vous pouvez également utiliser la méthode streamWithHeaders() pour définir les en-têtes avant de commencer le streaming.

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

    // vous pouvez ajouter n'importe quels en-têtes supplémentaires que vous voulez ici
    // vous devez juste utiliser header() ou Flight::response()->setRealHeader()

    // cependant que vous tirez vos données, juste comme exemple...
    $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 ',';
        }

        // Ceci est requis pour envoyer les données au client
        ob_flush();
    }
    echo '}';

// C'est ainsi que vous définirez les en-têtes avant de commencer le streaming.
})->streamWithHeaders([
    'Content-Type' => 'application/json',
    'Content-Disposition' => 'attachment; filename="users.json"',
    // code de statut optionnel, par défaut 200
    'status' => 200
]);

Voir aussi

Dépannage

404 Non trouvé ou comportement de route inattendu

Si vous voyez une erreur 404 Non trouvé (mais vous jurez sur votre vie que c'est vraiment là et que ce n'est pas une faute de frappe), cela pourrait en fait être un problème avec le fait que vous retournez une valeur dans votre point de terminaison de route au lieu de simplement l'échoer. La raison de cela est intentionnelle mais pourrait surprendre certains développeurs.

Flight::route('/hello', function(){
    // Cela pourrait causer une erreur 404 Non trouvé
    return 'Hello World';
});

// Ce que vous voulez probablement
Flight::route('/hello', function(){
    echo 'Hello World';
});

La raison de cela est en raison d'un mécanisme spécial intégré au routeur qui gère la sortie de retour comme un signal pour "aller à la route suivante". Vous pouvez voir le comportement documenté dans la section Routage.

Journal des modifications

Learn/learn

Découvrir Flight

Flight est un framework rapide, simple et extensible pour PHP. Il est très polyvalent et peut être utilisé pour construire tout type d'application web. Il est conçu avec la simplicité en tête et écrit de manière à être facile à comprendre et à utiliser.

Note : Vous verrez des exemples qui utilisent Flight:: comme une variable statique et d'autres qui utilisent l'objet Engine $app->. Les deux fonctionnent de manière interchangeable avec l'autre. $app et $this->app dans un contrôleur/middleware est l'approche recommandée par l'équipe Flight.

Composants de base

Routage

Apprenez à gérer les routes pour votre application web. Cela inclut également le groupement de routes, les paramètres de route et les middleware.

Middleware

Apprenez à utiliser les middleware pour filtrer les requêtes et les réponses dans votre application.

Autoloading

Apprenez à charger automatiquement vos propres classes dans votre application.

Requêtes

Apprenez à gérer les requêtes et les réponses dans votre application.

Réponses

Apprenez à envoyer des réponses à vos utilisateurs.

Modèles HTML

Apprenez à utiliser le moteur de vue intégré pour rendre vos modèles HTML.

Sécurité

Apprenez à sécuriser votre application contre les menaces de sécurité courantes.

Configuration

Apprenez à configurer le framework pour votre application.

Gestionnaire d'événements

Apprenez à utiliser le système d'événements pour ajouter des événements personnalisés à votre application.

Extension de Flight

Apprenez à étendre le framework en ajoutant vos propres méthodes et classes.

Crochets de méthodes et filtrage

Apprenez à ajouter des crochets d'événements à vos méthodes et aux méthodes internes du framework.

Conteneur d'injection de dépendances (DIC)

Apprenez à utiliser les conteneurs d'injection de dépendances (DIC) pour gérer les dépendances de votre application.

Classes utilitaires

Collections

Les collections sont utilisées pour stocker des données et y accéder comme un tableau ou un objet pour plus de simplicité.

Wrapper JSON

Cela comprend quelques fonctions simples pour rendre l'encodage et le décodage de votre JSON cohérents.

Wrapper PDO

PDO peut parfois causer plus de maux de tête que nécessaire. Cette classe wrapper simple peut rendre l'interaction avec votre base de données significativement plus facile.

Gestionnaire de fichiers téléchargés

Une classe simple pour aider à gérer les fichiers téléchargés et à les déplacer vers un emplacement permanent.

Concepts importants

Pourquoi un framework ?

Voici un court article sur pourquoi vous devriez utiliser un framework. Il est une bonne idée de comprendre les avantages d'utiliser un framework avant de commencer à en utiliser un.

De plus, un excellent tutoriel a été créé par @lubiana. Bien qu'il n'entre pas dans les détails spécifiques sur Flight, ce guide vous aidera à comprendre certains des concepts majeurs entourant un framework et pourquoi ils sont bénéfiques à utiliser. Vous pouvez trouver le tutoriel ici.

Flight comparé à d'autres frameworks

Si vous migrez d'un autre framework comme Laravel, Slim, Fat-Free ou Symfony vers Flight, cette page vous aidera à comprendre les différences entre les deux.

Autres sujets

Tests unitaires

Suivez ce guide pour apprendre à tester vos unités de code Flight de manière solide.

IA et expérience développeur

Apprenez comment Flight fonctionne avec les outils d'IA et les flux de travail modernes des développeurs pour vous aider à coder plus rapidement et plus intelligemment.

Migration v2 -> v3

La compatibilité descendante a été maintenue dans l'ensemble, mais il y a certains changements dont vous devriez être conscient lors de la migration de v2 à v3.

Learn/unit_testing

Tests unitaires

Aperçu

Les tests unitaires dans Flight vous aident à vous assurer que votre application se comporte comme prévu, à détecter les bogues tôt et à rendre votre codebase plus facile à maintenir. Flight est conçu pour fonctionner sans heurts avec PHPUnit, le framework de test PHP le plus populaire.

Comprendre

Les tests unitaires vérifient le comportement de petites parties de votre application (comme les contrôleurs ou les services) de manière isolée. Dans Flight, cela signifie tester comment vos routes, contrôleurs et logique répondent à différents inputs — sans dépendre d'un état global ou de services externes réels.

Principes clés :

Utilisation de base

Configuration de PHPUnit

  1. Installez PHPUnit avec Composer :
    composer require --dev phpunit/phpunit
  2. Créez un répertoire tests à la racine de votre projet.
  3. Ajoutez un script de test à votre composer.json :
    "scripts": {
       "test": "phpunit --configuration phpunit.xml"
    }
  4. Créez un fichier phpunit.xml :
    <?xml version="1.0" encoding="UTF-8"?>
    <phpunit bootstrap="vendor/autoload.php">
       <testsuites>
           <testsuite name="Flight Tests">
               <directory>tests</directory>
           </testsuite>
       </testsuites>
    </phpunit>

Maintenant, vous pouvez exécuter vos tests avec composer test.

Tester un gestionnaire de route simple

Supposons que vous ayez une route qui valide un e-mail :

// 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']);
    }
}

Un test simple pour ce contrôleur :

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']);
    }
}

Conseils :

Utiliser l'injection de dépendances pour des contrôleurs testables

Injectez les dépendances (comme la base de données ou l'envoi d'e-mails) dans vos contrôleurs pour les rendre faciles à simuler dans les tests :

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']);
    }
}

Et un test avec des simulations :

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

Utilisation avancée

Voir aussi

Dépannage

Journal des modifications

Learn/flight_vs_symfony

Vol Flight vs Symfony

Qu'est-ce que Symfony?

Symfony est un ensemble de composants PHP réutilisables et un framework PHP pour les projets web.

La base standard sur laquelle les meilleures applications PHP sont construites. Choisissez l'un des 50 composants autonomes disponibles pour vos propres applications.

Accélérez la création et la maintenance de vos applications web PHP. Mettez fin aux tâches de codage répétitives et profitez du pouvoir de contrôler votre code.

Avantages par rapport à Flight

Inconvénients par rapport à Flight

Learn/flight_vs_another_framework

Comparaison de Flight à un autre Framework

Si vous migrez d'un autre framework tel que Laravel, Slim, Fat-Free, ou Symfony vers Flight, cette page vous aidera à comprendre les différences entre les deux.

Laravel

Laravel est un framework complet qui possède toutes les fonctionnalités et une incroyable écosystème axé sur les développeurs, mais cela a un coût en termes de performances et de complexité.

Voir la comparaison entre Laravel et Flight.

Slim

Slim est un micro-framework similaire à Flight. Il est conçu pour être léger et facile à utiliser, mais peut être un peu plus complexe que Flight.

Voir la comparaison entre Slim et Flight.

Fat-Free

Fat-Free est un framework full-stack dans un package beaucoup plus petit. Bien qu'il possède tous les outils nécessaires, il a une architecture des données qui peut rendre certains projets plus complexes qu'ils ne le devraient.

Voir la comparaison entre Fat-Free et Flight.

Symfony

Symfony est un framework modulaire de niveau entreprise conçu pour être flexible et scalable. Pour les petits projets ou les nouveaux développeurs, Symfony peut être un peu intimidant.

Voir la comparaison entre Symfony et Flight.

Learn/pdo_wrapper

Classe d'Aide PDO PdoWrapper

Aperçu

La classe PdoWrapper dans Flight est un assistant convivial pour travailler avec les bases de données en utilisant PDO. Elle simplifie les tâches courantes de base de données, ajoute des méthodes pratiques pour récupérer les résultats, et retourne les résultats sous forme de Collections pour un accès facile. Elle supporte également la journalisation des requêtes et la surveillance des performances de l'application (APM) pour des cas d'utilisation avancés.

Comprendre

Travailler avec les bases de données en PHP peut être un peu verbeux, surtout en utilisant PDO directement. PdoWrapper étend PDO et ajoute des méthodes qui rendent les requêtes, la récupération et la gestion des résultats beaucoup plus faciles. Au lieu de jongler avec des instructions préparées et des modes de récupération, vous obtenez des méthodes simples pour les tâches courantes, et chaque ligne est retournée sous forme de Collection, afin que vous puissiez utiliser la notation tableau ou objet.

Vous pouvez enregistrer PdoWrapper en tant que service partagé dans Flight, puis l'utiliser n'importe où dans votre application via Flight::db().

Utilisation de Base

Enregistrement de l'Aideur PDO

D'abord, enregistrez la classe PdoWrapper avec 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
    ]
]);

Maintenant, vous pouvez utiliser Flight::db() n'importe où pour obtenir votre connexion à la base de données.

Exécution de Requêtes

runQuery()

function runQuery(string $sql, array $params = []): PDOStatement

Utilisez ceci pour les INSERT, UPDATE, ou lorsque vous voulez récupérer les résultats manuellement :

$db = Flight::db();
$statement = $db->runQuery("SELECT * FROM users WHERE status = ?", ['active']);
while ($row = $statement->fetch()) {
    // $row est un tableau
}

Vous pouvez également l'utiliser pour les écritures :

$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

Obtenez une seule valeur de la base de données :

$count = Flight::db()->fetchField("SELECT COUNT(*) FROM users WHERE status = ?", ['active']);

fetchRow()

function fetchRow(string $sql, array $params = []): Collection

Obtenez une seule ligne sous forme de Collection (accès tableau/objet) :

$user = Flight::db()->fetchRow("SELECT * FROM users WHERE id = ?", [123]);
echo $user['name'];
// ou
echo $user->name;

fetchAll()

function fetchAll(string $sql, array $params = []): array<Collection>

Obtenez toutes les lignes sous forme de tableau de Collections :

$users = Flight::db()->fetchAll("SELECT * FROM users WHERE status = ?", ['active']);
foreach ($users as $user) {
    echo $user['name'];
    // ou
    echo $user->name;
}

Utilisation des Lieux-Tenants IN()

Vous pouvez utiliser un seul ? dans une clause IN() et passer un tableau ou une chaîne séparée par des virgules :

$ids = [1, 2, 3];
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", [$ids]);
// ou
$users = Flight::db()->fetchAll("SELECT * FROM users WHERE id IN (?)", ['1,2,3']);

Utilisation Avancée

Journalisation des Requêtes & APM

Si vous voulez suivre les performances des requêtes, activez le suivi APM lors de l'enregistrement :

Flight::register('db', \flight\database\PdoWrapper::class, [
    'mysql:host=localhost;dbname=cool_db_name', 'user', 'pass', [/* options */], true // dernier paramètre active APM
]);

Après l'exécution des requêtes, vous pouvez les journaliser manuellement, mais l'APM les journalisera automatiquement si activé :

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

Cela déclenchera un événement (flight.db.queries) avec les métriques de connexion et de requête, que vous pouvez écouter en utilisant le système d'événements de Flight.

Exemple Complet

Flight::route('/users', function () {
    // Obtenir tous les utilisateurs
    $users = Flight::db()->fetchAll('SELECT * FROM users');

    // Diffuser tous les utilisateurs
    $statement = Flight::db()->runQuery('SELECT * FROM users');
    while ($user = $statement->fetch()) {
        echo $user['name'];
    }

    // Obtenir un seul utilisateur
    $user = Flight::db()->fetchRow('SELECT * FROM users WHERE id = ?', [123]);

    // Obtenir une seule valeur
    $count = Flight::db()->fetchField('SELECT COUNT(*) FROM users');

    // Syntaxe spéciale IN()
    $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']);

    // Insérer un nouvel utilisateur
    Flight::db()->runQuery("INSERT INTO users (name, email) VALUES (?, ?)", ['Bob', 'bob@example.com']);
    $insert_id = Flight::db()->lastInsertId();

    // Mettre à jour un utilisateur
    Flight::db()->runQuery("UPDATE users SET name = ? WHERE id = ?", ['Bob', 123]);

    // Supprimer un utilisateur
    Flight::db()->runQuery("DELETE FROM users WHERE id = ?", [123]);

    // Obtenir le nombre de lignes affectées
    $statement = Flight::db()->runQuery("UPDATE users SET name = ? WHERE name = ?", ['Bob', 'Sally']);
    $affected_rows = $statement->rowCount();
});

Voir Aussi

Dépannage

Journal des Modifications

Learn/dependency_injection_container

Conteneur d'injection de dépendances

Aperçu

Le Conteneur d'injection de dépendances (DIC) est une amélioration puissante qui vous permet de gérer les dépendances de votre application.

Comprendre

L'injection de dépendances (DI) est un concept clé dans les frameworks PHP modernes et est utilisée pour gérer l'instanciation et la configuration des objets. Certains exemples de bibliothèques DIC sont : flightphp/container, Dice, Pimple, PHP-DI, et league/container.

Un DIC est une façon élégante de vous permettre de créer et de gérer vos classes dans un emplacement centralisé. Cela est utile lorsque vous devez passer le même objet à plusieurs classes (comme vos contrôleurs ou middleware par exemple).

Utilisation de base

L'ancienne façon de faire les choses pourrait ressembler à ceci :


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

Vous pouvez voir dans le code ci-dessus que nous créons un nouvel objet PDO et le passons à notre classe UserController. Cela est correct pour une petite application, mais à mesure que votre application grandit, vous constaterez que vous créez ou passez le même objet PDO à plusieurs endroits. C'est là qu'un DIC entre en jeu de manière pratique.

Voici le même exemple en utilisant un DIC (avec 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();

Je parie que vous pensez peut-être qu'il y a beaucoup de code supplémentaire ajouté à l'exemple. La magie vient quand vous avez un autre contrôleur qui a besoin de l'objet PDO.


// 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' ]);

L'avantage supplémentaire d'utiliser un DIC est que les tests unitaires deviennent beaucoup plus faciles. Vous pouvez créer un objet mock et le passer à votre classe. C'est un énorme avantage lorsque vous écrivez des tests pour votre application !

Créer un gestionnaire DIC centralisé

Vous pouvez créer un gestionnaire DIC centralisé dans votre fichier de services en étendant votre application. Voici un exemple :

// 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 dispose d'un plugin qui fournit un conteneur simple conforme à PSR-11 que vous pouvez utiliser pour gérer votre injection de dépendances. Voici un exemple rapide de son utilisation :


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

Utilisation avancée de flightphp/container

Vous pouvez également résoudre les dépendances de manière récursive. Voici un exemple :

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

Vous pouvez également créer votre propre gestionnaire DIC. Cela est utile si vous avez un conteneur personnalisé que vous souhaitez utiliser qui n'est pas conforme à PSR-11 (Dice). Voir la section d'utilisation de base pour savoir comment faire.

De plus, il y a des valeurs par défaut utiles qui rendront votre vie plus facile lors de l'utilisation de Flight.

Instance Engine

Si vous utilisez l'instance Engine dans vos contrôleurs/middleware, voici comment vous la configureriez :


// 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');
    }
}

Ajouter d'autres classes

Si vous avez d'autres classes que vous souhaitez ajouter au conteneur, avec Dice c'est facile car elles seront automatiquement résolues par le conteneur. Voici un exemple :


$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 peut également utiliser n'importe quel conteneur conforme à PSR-11. Cela signifie que vous pouvez utiliser n'importe quel conteneur qui implémente l'interface PSR-11. Voici un exemple en utilisant le conteneur PSR-11 de League :


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

Cela peut être un peu plus verbeux que l'exemple précédent avec Dice, cela accomplit toujours la tâche avec les mêmes avantages !

Voir aussi

Dépannage

Journal des modifications

Learn/middleware

Middleware

Aperçu

Flight prend en charge les middlewares de route et de groupe de routes. Le middleware est une partie de votre application où le code est exécuté avant (ou après) le rappel de la route. C'est une excellente façon d'ajouter des vérifications d'authentification API dans votre code, ou de valider que l'utilisateur a la permission d'accéder à la route.

Comprendre

Les middlewares peuvent grandement simplifier votre application. Au lieu d'une inheritance de classes abstraites complexes ou de substitutions de méthodes, les middlewares vous permettent de contrôler vos routes en assignant votre logique d'application personnalisée. Vous pouvez penser aux middlewares comme à un sandwich. Vous avez du pain à l'extérieur, et puis des couches de garnitures comme de la laitue, des tomates, de la viande et du fromage. Imaginez que chaque requête est comme prendre une bouchée du sandwich où vous mangez les couches extérieures en premier et progressez vers le cœur.

Voici une visualisation de la façon dont les middlewares fonctionnent. Ensuite, nous vous montrerons un exemple pratique de son fonctionnement.

Requête utilisateur à l'URL /api ----> 
    Middleware->before() exécuté ----->
        Fonction callable/méthode attachée à /api exécutée et réponse générée ------>
    Middleware->after() exécuté ----->
L'utilisateur reçoit la réponse du serveur

Et voici un exemple pratique :

L'utilisateur navigue vers l'URL /dashboard
    LoggedInMiddleware->before() s'exécute
        before() vérifie une session connectée valide
            si oui, ne rien faire et continuer l'exécution
            si non, rediriger l'utilisateur vers /login
                Fonction callable/méthode attachée à /api exécutée et réponse générée
    LoggedInMiddleware->after() n'a rien de défini donc il laisse l'exécution continuer
L'utilisateur reçoit le HTML du tableau de bord du serveur

Ordre d'exécution

Les fonctions de middleware sont exécutées dans l'ordre où elles sont ajoutées à la route. L'exécution est similaire à la façon dont Slim Framework gère cela.

Les méthodes before() sont exécutées dans l'ordre d'ajout, et les méthodes after() sont exécutées en ordre inverse.

Ex : Middleware1->before(), Middleware2->before(), Middleware2->after(), Middleware1->after().

Utilisation de base

Vous pouvez utiliser des middlewares comme n'importe quelle méthode callable, y compris une fonction anonyme ou une classe (recommandé)

Fonction anonyme

Voici un exemple simple :

Flight::route('/path', function() { echo ' Here I am!'; })->addMiddleware(function() {
    echo 'Middleware first!';
});

Flight::start();

// Cela affichera "Middleware first! Here I am!"

Note : Lorsque vous utilisez une fonction anonyme, la seule méthode interprétée est une méthode before(). Vous ne pouvez pas définir un comportement after() avec une classe anonyme.

Utilisation de classes

Les middlewares peuvent (et devraient) être enregistrés comme une classe. Si vous avez besoin de la fonctionnalité "after", vous devez utiliser une classe.

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); 
// aussi ->addMiddleware([ $MyMiddleware, $MyMiddleware2 ]);

Flight::start();

// Cela affichera "Middleware first! Here I am! Middleware last!"

Vous pouvez aussi simplement définir le nom de la classe de middleware et elle instanciera la classe.

Flight::route('/path', function() { echo ' Here I am! '; })->addMiddleware(MyMiddleware::class); 

Note : Si vous passez simplement le nom du middleware, il sera automatiquement exécuté par le conteneur d'injection de dépendances et le middleware sera exécuté avec les paramètres dont il a besoin. Si vous n'avez pas de conteneur d'injection de dépendances enregistré, il passera par défaut l'instance flight\Engine dans le __construct(Engine $app).

Utilisation de routes avec paramètres

Si vous avez besoin de paramètres de votre route, ils seront passés dans un seul tableau à votre fonction de middleware. (function($params) { ... } ou public function before($params) { ... }). La raison en est que vous pouvez structurer vos paramètres en groupes et dans certains de ces groupes, vos paramètres peuvent apparaître dans un ordre différent, ce qui casserait la fonction de middleware en se référant au mauvais paramètre. De cette façon, vous pouvez y accéder par nom au lieu de position.

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 peut ou non être passé
        $jobId = $params['jobId'] ?? 0;

        // peut-être que s'il n'y a pas d'ID de job, vous n'avez pas besoin de rechercher quoi que ce soit.
        if($jobId === 0) {
            return;
        }

        // effectuer une recherche de quelque sorte dans votre base de données
        $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) {

    // Ce groupe ci-dessous obtient toujours le middleware parent
    // Mais les paramètres sont passés dans un seul tableau 
    // dans le middleware.
    $router->group('/job/@jobId', function(Router $router) {
        $router->get('', [ JobController::class, 'view' ]);
        $router->put('', [ JobController::class, 'update' ]);
        $router->delete('', [ JobController::class, 'delete' ]);
        // plus de routes...
    });
}, [ RouteSecurityMiddleware::class ]);

Groupement de routes avec middleware

Vous pouvez ajouter un groupe de routes, et puis chaque route dans ce groupe aura le même middleware. C'est utile si vous avez besoin de grouper un tas de routes par un middleware Auth pour vérifier la clé API dans l'en-tête.


// ajouté à la fin de la méthode group
Flight::group('/api', function() {

    // Cette route "vide" correspondra en fait à /api
    Flight::route('', function() { echo 'api'; }, false, 'api');
    // Cela correspondra à /api/users
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // Cela correspondra à /api/users/1234
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ new ApiAuthMiddleware() ]);

Si vous voulez appliquer un middleware global à toutes vos routes, vous pouvez ajouter un groupe "vide" :


// ajouté à la fin de la méthode group
Flight::group('', function() {

    // C'est toujours /users
    Flight::route('/users', function() { echo 'users'; }, false, 'users');
    // Et cela reste /users/1234
    Flight::route('/users/@id', function($id) { echo 'user:'.$id; }, false, 'user_view');
}, [ ApiAuthMiddleware::class ]); // ou [ new ApiAuthMiddleware() ], c'est la même chose

Cas d'utilisation courants

Validation de clé API

Si vous vouliez protéger vos routes /api en vérifiant que la clé API est correcte, vous pouvez facilement gérer cela avec un middleware.

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

        // effectuer une recherche dans votre base de données pour la clé api
        $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' ]);
    // plus de routes...
}, [ ApiMiddleware::class ]);

Maintenant toutes vos routes API sont protégées par ce middleware de validation de clé API que vous avez configuré ! Si vous ajoutez plus de routes dans le groupe de routeur, elles auront instantanément la même protection !

Validation de connexion

Voulez-vous protéger certaines routes pour qu'elles ne soient disponibles que pour les utilisateurs connectés ? Cela peut facilement être réalisé avec un middleware !

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' ]);
    // plus de routes...
}, [ LoggedInMiddleware::class ]);

Validation de paramètres de route

Voulez-vous protéger vos utilisateurs en changeant les valeurs dans l'URL pour accéder à des données qu'ils ne devraient pas ? Cela peut être résolu avec un middleware !

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'];

        // effectuer une recherche de quelque sorte dans votre base de données
        $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' ]);
    // plus de routes...
}, [ RouteSecurityMiddleware::class ]);

Gestion de l'exécution des middlewares

Disons que vous avez un middleware d'authentification et que vous voulez rediriger l'utilisateur vers une page de connexion s'il n'est pas authentifié. Vous avez plusieurs options à votre disposition :

  1. Vous pouvez retourner false de la fonction middleware et Flight retournera automatiquement une erreur 403 Forbidden, mais sans personnalisation.
  2. Vous pouvez rediriger l'utilisateur vers une page de connexion en utilisant Flight::redirect().
  3. Vous pouvez créer une erreur personnalisée dans le middleware et arrêter l'exécution de la route.

Simple et direct

Voici un exemple simple de return false; :

class MyMiddleware {
    public function before($params) {
        $hasUserKey = Flight::session()->exists('user');
        if ($hasUserKey === false) {
            return false;
        }

        // puisque c'est vrai, tout continue simplement
    }
}

Exemple de redirection

Voici un exemple de redirection de l'utilisateur vers une page de connexion :

class MyMiddleware {
    public function before($params) {
        $hasUserKey = Flight::session()->exists('user');
        if ($hasUserKey === false) {
            Flight::redirect('/login');
            exit;
        }
    }
}

Exemple d'erreur personnalisée

Disons que vous avez besoin de lancer une erreur JSON parce que vous construisez une API. Vous pouvez faire cela comme ceci :

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);
            // ou
            Flight::json(['error' => 'You must be logged in to access this page.'], 403);
            exit;
            // ou
            Flight::halt(403, json_encode(['error' => 'You must be logged in to access this page.']);
        }
    }
}

Voir aussi

Dépannage

Changelog

Learn/filtering

Filtrage

Aperçu

Flight vous permet de filtrer les méthodes mappées avant et après leur appel.

Comprendre

Il n'y a pas de hooks prédéfinis que vous devez mémoriser. Vous pouvez filtrer n'importe laquelle des méthodes par défaut du framework ainsi que n'importe quelles méthodes personnalisées que vous avez mappées.

Une fonction de filtre ressemble à ceci :

/**
 * @param array $params Les paramètres passés à la méthode filtrée.
 * @param string $output (v2 buffering de sortie uniquement) La sortie de la méthode filtrée.
 * @return bool Retournez true/vide ou ne retournez rien pour continuer la chaîne, false pour interrompre la chaîne.
 */
function (array &$params, string &$output): bool {
  // Code de filtre
}

En utilisant les variables passées, vous pouvez manipuler les paramètres d'entrée et/ou la sortie.

Vous pouvez faire exécuter un filtre avant une méthode en faisant :

Flight::before('start', function (array &$params, string &$output): bool {
  // Faites quelque chose
});

Vous pouvez faire exécuter un filtre après une méthode en faisant :

Flight::after('start', function (array &$params, string &$output): bool {
  // Faites quelque chose
});

Vous pouvez ajouter autant de filtres que vous voulez à n'importe quelle méthode. Ils seront appelés dans l'ordre où ils sont déclarés.

Voici un exemple du processus de filtrage :

// Mappez une méthode personnalisée
Flight::map('hello', function (string $name) {
  return "Hello, $name!";
});

// Ajoutez un filtre avant
Flight::before('hello', function (array &$params, string &$output): bool {
  // Manipulez le paramètre
  $params[0] = 'Fred';
  return true;
});

// Ajoutez un filtre après
Flight::after('hello', function (array &$params, string &$output): bool {
  // Manipulez la sortie
  $output .= " Have a nice day!";
  return true;
});

// Invoquez la méthode personnalisée
echo Flight::hello('Bob');

Cela devrait afficher :

Hello Fred! Have a nice day!

Si vous avez défini plusieurs filtres, vous pouvez interrompre la chaîne en retournant false dans l'une de vos fonctions de filtre :

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'one';
  return true;
});

Flight::before('start', function (array &$params, string &$output): bool {
  echo 'two';

  // Cela mettra fin à la chaîne
  return false;
});

// Ceci ne sera pas appelé
Flight::before('start', function (array &$params, string &$output): bool {
  echo 'three';
  return true;
});

Note : Les méthodes de base telles que map et register ne peuvent pas être filtrées car elles sont appelées directement et non invoquées dynamiquement. Voir Extending Flight pour plus d'informations.

Voir aussi

Dépannage

Journal des modifications

Learn/requests

Requêtes

Aperçu

Flight encapsule la requête HTTP dans un seul objet, qui peut être accédé en faisant :

$request = Flight::request();

Compréhension

Les requêtes HTTP sont l'un des aspects fondamentaux à comprendre concernant le cycle de vie HTTP. Un utilisateur effectue une action dans un navigateur web ou un client HTTP, et ils envoient une série d'en-têtes, de corps, d'URL, etc. vers votre projet. Vous pouvez capturer ces en-têtes (le langage du navigateur, le type de compression qu'ils peuvent gérer, l'agent utilisateur, etc.) et capturer le corps et l'URL qui sont envoyés à votre application Flight. Ces requêtes sont essentielles pour que votre application comprenne quoi faire ensuite.

Utilisation de base

PHP possède plusieurs super globales incluant $_GET, $_POST, $_REQUEST, $_SERVER, $_FILES, et $_COOKIE. Flight abstrait ces éléments en Collections pratiques. Vous pouvez accéder aux propriétés query, data, cookies, et files en tant qu'arrays ou objets.

Note : Il est FORTEMENT déconseillé d'utiliser ces super globales dans votre projet et elles doivent être référencées via l'objet request().

Note : Il n'y a pas d'abstraction disponible pour $_ENV.

$_GET

Vous pouvez accéder à l'array $_GET via la propriété query :

// GET /search?keyword=something
Flight::route('/search', function(){
    $keyword = Flight::request()->query['keyword'];
    // ou
    $keyword = Flight::request()->query->keyword;
    echo "Vous recherchez : $keyword";
    // interroger une base de données ou autre chose avec le $keyword
});

$_POST

Vous pouvez accéder à l'array $_POST via la propriété data :

Flight::route('POST /submit', function(){
    $name = Flight::request()->data['name'];
    $email = Flight::request()->data['email'];
    // ou
    $name = Flight::request()->data->name;
    $email = Flight::request()->data->email;
    echo "Vous avez soumis : $name, $email";
    // sauvegarder dans une base de données ou autre chose avec le $name et $email
});

$_COOKIE

Vous pouvez accéder à l'array $_COOKIE via la propriété cookies :

Flight::route('GET /login', function(){
    $savedLogin = Flight::request()->cookies['myLoginCookie'];
    // ou
    $savedLogin = Flight::request()->cookies->myLoginCookie;
    // vérifier s'il est vraiment sauvegardé ou non et si oui, les connecter automatiquement
    if($savedLogin) {
        Flight::redirect('/dashboard');
        return;
    }
});

Pour de l'aide sur la définition de nouvelles valeurs de cookies, voir overclokk/cookie

$_SERVER

Il existe un raccourci disponible pour accéder à l'array $_SERVER via la méthode getVar() :


$host = Flight::request()->getVar('HTTP_HOST');

$_FILES

Vous pouvez accéder aux fichiers téléchargés via la propriété files :

// accès brut à la propriété $_FILES. Voir ci-dessous pour l'approche recommandée
$uploadedFile = Flight::request()->files['myFile']; 
// ou
$uploadedFile = Flight::request()->files->myFile;

Voir Uploaded File Handler pour plus d'informations.

Traitement des téléchargements de fichiers

v3.12.0

Vous pouvez traiter les téléchargements de fichiers en utilisant le framework avec quelques méthodes d'aide. Cela se résume essentiellement à extraire les données de fichier de la requête et à les déplacer vers un nouvel emplacement.

Flight::route('POST /upload', function(){
    // Si vous aviez un champ d'entrée comme <input type="file" name="myFile">
    $uploadedFileData = Flight::request()->getUploadedFiles();
    $uploadedFile = $uploadedFileData['myFile'];
    $uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
});

Si vous avez plusieurs fichiers téléchargés, vous pouvez les parcourir en boucle :

Flight::route('POST /upload', function(){
    // Si vous aviez un champ d'entrée comme <input type="file" name="myFiles[]">
    $uploadedFiles = Flight::request()->getUploadedFiles()['myFiles'];
    foreach ($uploadedFiles as $uploadedFile) {
        $uploadedFile->moveTo('/path/to/uploads/' . $uploadedFile->getClientFilename());
    }
});

Note de sécurité : Validez et nettoyez toujours les entrées utilisateur, surtout lors du traitement des téléchargements de fichiers. Validez toujours le type d'extensions que vous autorisez à être téléchargées, mais vous devriez également valider les "octets magiques" du fichier pour vous assurer qu'il s'agit réellement du type de fichier que l'utilisateur prétend. Il existe des articles et bibliothèques disponibles pour vous aider avec cela.

Corps de la requête

Pour obtenir le corps brut de la requête HTTP, par exemple lors du traitement de requêtes POST/PUT, vous pouvez faire :

Flight::route('POST /users/xml', function(){
    $xmlBody = Flight::request()->getBody();
    // faire quelque chose avec le XML qui a été envoyé.
});

Corps JSON

Si vous recevez une requête avec le type de contenu application/json et les données d'exemple {"id": 123} elle sera disponible via la propriété data :

$id = Flight::request()->data->id;

En-têtes de la requête

Vous pouvez accéder aux en-têtes de la requête en utilisant la méthode getHeader() ou getHeaders() :


// Peut-être que vous avez besoin de l'en-tête Authorization
$host = Flight::request()->getHeader('Authorization');
// ou
$host = Flight::request()->header('Authorization');

// Si vous devez récupérer tous les en-têtes
$headers = Flight::request()->getHeaders();
// ou
$headers = Flight::request()->headers();

Méthode de la requête

Vous pouvez accéder à la méthode de la requête en utilisant la propriété method ou la méthode getMethod() :

$method = Flight::request()->method; // en réalité peuplé par getMethod()
$method = Flight::request()->getMethod();

Note : La méthode getMethod() récupère d'abord la méthode depuis $_SERVER['REQUEST_METHOD'], puis elle peut être écrasée par $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] si elle existe ou $_REQUEST['_method'] si elle existe.

Propriétés de l'objet requête

L'objet requête fournit les propriétés suivantes :

Méthodes d'aide

Il existe quelques méthodes d'aide pour assembler des parties d'une URL, ou gérer certains en-têtes.

URL complète

Vous pouvez accéder à l'URL complète de la requête en utilisant la méthode getFullUrl() :

$url = Flight::request()->getFullUrl();
// https://example.com/some/path?foo=bar

URL de base

Vous pouvez accéder à l'URL de base en utilisant la méthode getBaseUrl() :

// http://example.com/path/to/something/cool?query=yes+thanks
$url = Flight::request()->getBaseUrl();
// https://example.com
// Notez, pas de barre oblique finale.

Analyse de requête

Vous pouvez passer une URL à la méthode parseQuery() pour analyser la chaîne de requête en un array associatif :

$query = Flight::request()->parseQuery('https://example.com/some/path?foo=bar');
// ['foo' => 'bar']

Négociation des types de contenu acceptés

v3.17.2

Vous pouvez utiliser la méthode negotiateContentType() pour déterminer le meilleur type de contenu à répondre en fonction de l'en-tête Accept envoyé par le client.


// Exemple d'en-tête Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
// Le code ci-dessous définit ce que vous supportez.
$availableTypes = ['application/json', 'application/xml'];
$typeToServe = Flight::request()->negotiateContentType($availableTypes);
if ($typeToServe === 'application/json') {
    // Servir une réponse JSON
} elseif ($typeToServe === 'application/xml') {
    // Servir une réponse XML
} else {
    // Par défaut, utiliser quelque chose d'autre ou lever une erreur
}

Note : Si aucun des types disponibles n'est trouvé dans l'en-tête Accept, la méthode renverra null. Si aucun en-tête Accept n'est défini, la méthode renverra le premier type dans le array $availableTypes.

Voir aussi

Dépannage

Journal des modifications

Learn/why_frameworks

Pourquoi un Framework?

Certains programmeurs sont farouchement opposés à l'utilisation de frameworks. Ils soutiennent que les frameworks sont gonflés, lents et difficiles à apprendre. Ils disent que les frameworks sont inutiles et que vous pouvez écrire un meilleur code sans eux. Il y a certainement quelques points valides à faire valoir sur les inconvénients de l'utilisation des frameworks. Cependant, il y a aussi de nombreux avantages à utiliser des frameworks.

Raisons d'utiliser un Framework

Voici quelques raisons pour lesquelles vous pourriez envisager d'utiliser un framework:

Flight est un micro-framework. Cela signifie qu'il est petit et léger. Il ne fournit pas autant de fonctionnalités que des frameworks plus grands comme Laravel ou Symfony. Cependant, il fournit une grande partie des fonctionnalités dont vous avez besoin pour construire des applications web. Il est également facile à apprendre et à utiliser. Cela en fait un bon choix pour construire rapidement et facilement des applications web. Si vous êtes novice en frameworks, Flight est un excellent framework pour débutants. Cela vous aidera à découvrir les avantages de l'utilisation des frameworks sans vous submerger avec trop de complexité. Après avoir acquis de l'expérience avec Flight, il sera plus facile de passer à des frameworks plus complexes comme Laravel ou Symfony, cependant Flight peut toujours vous aider à créer une application robuste et réussie.

Qu'est-ce que le Routage?

Le routage est au cœur du framework Flight, mais qu'est-ce que c'est exactement? Le routage est le processus de prendre une URL et de la faire correspondre à une fonction spécifique dans votre code. C'est ainsi que vous pouvez faire en sorte que votre site Web fasse différentes choses en fonction de l'URL demandée. Par exemple, vous pouvez vouloir afficher le profil d'un utilisateur lorsqu'il visite /utilisateur/1234, mais affichez une liste de tous les utilisateurs lorsqu'ils visitent /utilisateurs. Tout cela se fait grâce au routage.

Cela pourrait fonctionner un peu comme ceci:

Et Pourquoi est-ce Important?

Avoir un routeur centralisé approprié peut réellement rendre votre vie beaucoup plus facile! Il peut être difficile de le voir au début. Voici quelques raisons:

Je suis sûr que vous êtes familier avec la manière scriptée de créer un site web. Vous pourriez avoir un fichier appelé index.php qui comporte une multitude de déclarations if pour vérifier l'URL et puis exécuter une fonction spécifique en fonction de l'URL. C'est une forme de routage, mais ce n'est pas très organisé et cela peut rapidement devenir incontrôlable. Le système de routage de Flight est une méthode beaucoup plus organisée et puissante pour gérer le routage.

Comme ceci ?


// /utilisateur/voir_profil.php?id=1234
if ($_GET['id']) {
    $id = $_GET['id'];
    voirProfilUtilisateur($id);
}

// /utilisateur/editer_profil.php?id=1234
if ($_GET['id']) {
    $id = $_GET['id'];
    editerProfilUtilisateur($id);
}

// etc...

Ou comme cela ?


// index.php
Flight::route('/utilisateur/@id', [ 'ControlleurUtilisateur', 'voirProfilUtilisateur' ]);
Flight::route('/utilisateur/@id/editer', [ 'ControlleurUtilisateur', 'editerProfilUtilisateur' ]);

// Peut-être dans votre app/controllers/ControlleurUtilisateur.php
class ControlleurUtilisateur {
    public function voirProfilUtilisateur($id) {
        // faire quelque chose
    }

    public function editerProfilUtilisateur($id) {
        // faire quelque chose
    }
}

Espérons que vous commencez à voir les avantages d'utiliser un système de routage centralisé. C'est beaucoup plus facile à gérer et à comprendre à long terme!

Demandes et Réponses

Flight fournit un moyen simple et facile de gérer les demandes et les réponses. C'est le cœur de ce qu'un framework web fait. Il prend une demande de navigateur d'utilisateur, la traite, puis envoie une réponse. C'est ainsi que vous pouvez construire des applications web qui font des choses comme afficher un profil d'utilisateur, permettre à un utilisateur de se connecter, ou permettre à un utilisateur de poster un nouveau billet de blog.

Demandes

Une demande est ce que le navigateur de l'utilisateur envoie à votre serveur lorsqu'ils visitent votre site. Cette demande contient des informations sur ce que l'utilisateur veut faire. Par exemple, elle peut contenir des informations sur l'URL que l'utilisateur souhaite visiter, les données que l'utilisateur souhaite envoyer à votre serveur, ou le type de données que l'utilisateur souhaite recevoir de votre serveur. Il est important de savoir qu'une demande est en lecture seule. Vous ne pouvez pas changer la demande, mais vous pouvez la lire.

Flight fournit un moyen simple d'accéder aux informations sur la demande. Vous pouvez accéder aux informations sur la demande en utilisant la méthode Flight::request() Cette méthode renvoie un objet Request qui contient des informations sur la demande. Vous pouvez utiliser cet objet pour accéder aux informations sur la demande, telles que l'URL, la méthode, ou les données que l'utilisateur a envoyées à votre serveur.

Réponses

Une réponse est ce que votre serveur envoie à nouveau au navigateur de l'utilisateur lorsqu'ils visitent votre site. Cette réponse contient des informations sur ce que votre serveur veut faire. Par exemple, elle peut contenir des informations sur le type de données que votre serveur souhaite envoyer à l'utilisateur, le type de données que votre serveur souhaite recevoir de l'utilisateur, ou le type de données que votre serveur souhaite stocker sur l'ordinateur de l'utilisateur.

Flight fournit un moyen simple d'envoyer une réponse au navigateur de l'utilisateur. Vous pouvez envoyer une réponse en utilisant la méthode Flight::response() Cette méthode prend un objet Response en argument et envoie la réponse au navigateur de l'utilisateur. Vous pouvez utiliser cet objet pour envoyer une réponse au navigateur de l'utilisateur, telle que HTML, JSON ou un fichier. Flight vous aide à générer automatiquement certaines parties de la réponse pour faciliter les choses, mais finalement vous avez le contrôle sur ce que vous renvoyez à l'utilisateur.

Learn/responses

Réponses

Aperçu

Flight aide à générer une partie des en-têtes de réponse pour vous, mais vous conservez la plupart du contrôle sur ce que vous renvoyez à l'utilisateur. La plupart du temps, vous accédez directement à l'objet response(), mais Flight propose des méthodes d'assistance pour définir certains en-têtes de réponse pour vous.

Comprendre

Après que l'utilisateur a envoyé sa requête à votre application, vous devez générer une réponse appropriée pour lui. Ils vous ont envoyé des informations comme la langue qu'ils préfèrent, s'ils peuvent gérer certains types de compression, leur agent utilisateur, etc., et après avoir traité tout cela, il est temps de leur renvoyer une réponse appropriée. Cela peut consister à définir des en-têtes, à sortir un corps de HTML ou de JSON pour eux, ou à les rediriger vers une page.

Utilisation de base

Envoyer un corps de réponse

Flight utilise ob_start() pour tamponner la sortie. Cela signifie que vous pouvez utiliser echo ou print pour envoyer une réponse à l'utilisateur et que Flight la capturera et la renverra à l'utilisateur avec les en-têtes appropriés.

// Cela enverra "Hello, World!" au navigateur de l'utilisateur
Flight::route('/', function() {
    echo "Hello, World!";
});

// HTTP/1.1 200 OK
// Content-Type: text/html
//
// Hello, World!

En alternative, vous pouvez appeler la méthode write() pour ajouter au corps également.

// Cela enverra "Hello, World!" au navigateur de l'utilisateur
Flight::route('/', function() {
    // verbeux, mais cela fait le travail parfois quand vous en avez besoin
    Flight::response()->write("Hello, World!");

    // si vous voulez récupérer le corps que vous avez défini à ce stade
    // vous pouvez le faire comme ceci
    $body = Flight::response()->getBody();
});

JSON

Flight fournit un support pour envoyer des réponses JSON et JSONP. Pour envoyer une réponse JSON, vous passez des données à encoder en JSON :

Flight::route('/@companyId/users', function(int $companyId) {
    // d'une manière ou d'une autre, extrayez vos utilisateurs d'une base de données par exemple
    $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"}, /* plus d'utilisateurs */ ]

Note : Par défaut, Flight enverra un en-tête Content-Type: application/json avec la réponse. Il utilisera également les drapeaux JSON_THROW_ON_ERROR et JSON_UNESCAPED_SLASHES lors de l'encodage du JSON.

JSON avec code de statut

Vous pouvez également passer un code de statut en tant que deuxième argument :

Flight::json(['id' => 123], 201);

JSON avec impression formatée

Vous pouvez également passer un argument à la dernière position pour activer l'impression formatée :

Flight::json(['id' => 123], 200, true, 'utf-8', JSON_PRETTY_PRINT);

Changer l'ordre des arguments JSON

Flight::json() est une méthode très ancienne, mais l'objectif de Flight est de maintenir la compatibilité arrière pour les projets. C'est en fait très simple si vous voulez refaire l'ordre des arguments pour utiliser une syntaxe plus simple, vous pouvez simplement remapper la méthode JSON comme n'importe quelle autre méthode Flight :

Flight::map('json', function($data, $code = 200, $options = 0) {

    // maintenant vous n'avez pas besoin de `true, 'utf-8'` quand vous utilisez la méthode json() !
    Flight::_json($data, $code, true, 'utf-8', $options);
}

// Et maintenant cela peut être utilisé comme ceci
Flight::json(['id' => 123], 200, JSON_PRETTY_PRINT);

JSON et arrêt de l'exécution

v3.10.0

Si vous voulez envoyer une réponse JSON et arrêter l'exécution, vous pouvez utiliser la méthode jsonHalt(). C'est utile pour les cas où vous vérifiez peut-être un type d'autorisation et si l'utilisateur n'est pas autorisé, vous pouvez envoyer une réponse JSON immédiatement, effacer le contenu du corps existant et arrêter l'exécution.

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // Vérifier si l'utilisateur est autorisé
    if($authorized === false) {
        Flight::jsonHalt(['error' => 'Unauthorized'], 401);
        // pas de sortie ; nécessaire ici.
    }

    // Continuer avec le reste de la route
});

Avant v3.10.0, vous deviez faire quelque chose comme ceci :

Flight::route('/users', function() {
    $authorized = someAuthorizationCheck();
    // Vérifier si l'utilisateur est autorisé
    if($authorized === false) {
        Flight::halt(401, json_encode(['error' => 'Unauthorized']));
    }

    // Continuer avec le reste de la route
});

Effacer un corps de réponse

Si vous voulez effacer le corps de réponse, vous pouvez utiliser la méthode clearBody :

Flight::route('/', function() {
    if($someCondition) {
        Flight::response()->write("Hello, World!");
    } else {
        Flight::response()->clearBody();
    }
});

Le cas d'utilisation ci-dessus n'est probablement pas courant, cependant il pourrait être plus courant si cela était utilisé dans un middleware.

Exécuter un rappel sur le corps de réponse

Vous pouvez exécuter un rappel sur le corps de réponse en utilisant la méthode addResponseBodyCallback :

Flight::route('/users', function() {
    $db = Flight::db();
    $users = $db->fetchAll("SELECT * FROM users");
    Flight::render('users_table', ['users' => $users]);
});

// Cela gzippera toutes les réponses pour n'importe quelle route
Flight::response()->addResponseBodyCallback(function($body) {
    return gzencode($body, 9);
});

Vous pouvez ajouter plusieurs rappels et ils seront exécutés dans l'ordre où ils ont été ajoutés. Comme cela peut accepter n'importe quel appelable, il peut accepter un tableau de classe [ $class, 'method' ], une closure $strReplace = function($body) { str_replace('hi', 'there', $body); };, ou un nom de fonction 'minify' si vous aviez une fonction pour minifier votre code HTML par exemple.

Note : Les rappels de route ne fonctionneront pas si vous utilisez l'option de configuration flight.v2.output_buffering.

Rappel de route spécifique

Si vous vouliez que cela ne s'applique qu'à une route spécifique, vous pourriez ajouter le rappel dans la route elle-même :

Flight::route('/users', function() {
    $db = Flight::db();
    $users = $db->fetchAll("SELECT * FROM users");
    Flight::render('users_table', ['users' => $users]);

    // Cela gzippera seulement la réponse pour cette route
    Flight::response()->addResponseBodyCallback(function($body) {
        return gzencode($body, 9);
    });
});

Option Middleware

Vous pouvez également utiliser le middleware pour appliquer le rappel à toutes les routes via le middleware :

// MinifyMiddleware.php
class MinifyMiddleware {
    public function before() {
        // Appliquer le rappel ici sur l'objet response().
        Flight::response()->addResponseBodyCallback(function($body) {
            return $this->minify($body);
        });
    }

    protected function minify(string $body): string {
        // minifier le corps d'une manière ou d'une autre
        return $body;
    }
}

// index.php
Flight::group('/users', function() {
    Flight::route('', function() { /* ... */ });
    Flight::route('/@id', function($id) { /* ... */ });
}, [ new MinifyMiddleware() ]);

Codes de statut

Vous pouvez définir le code de statut de la réponse en utilisant la méthode status :

Flight::route('/@id', function($id) {
    if($id == 123) {
        Flight::response()->status(200);
        echo "Hello, World!";
    } else {
        Flight::response()->status(403);
        echo "Forbidden";
    }
});

Si vous voulez obtenir le code de statut actuel, vous pouvez utiliser la méthode status sans aucun argument :

Flight::response()->status(); // 200

Définir un en-tête de réponse

Vous pouvez définir un en-tête tel que le type de contenu de la réponse en utilisant la méthode header :

// Cela enverra "Hello, World!" au navigateur de l'utilisateur en texte brut
Flight::route('/', function() {
    Flight::response()->header('Content-Type', 'text/plain');
    // ou
    Flight::response()->setHeader('Content-Type', 'text/plain');
    echo "Hello, World!";
});

Redirection

Vous pouvez rediriger la requête actuelle en utilisant la méthode redirect() et en passant un nouvel URL :

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; // ceci est nécessaire pour que la fonctionnalité ci-dessous ne s'exécute pas
    }

    // ajouter le nouvel utilisateur...
    Flight::db()->runQuery("INSERT INTO users ....");
    Flight::redirect('/admin/dashboard');
});

Note : Par défaut, Flight envoie un code de statut HTTP 303 ("See Other"). Vous pouvez optionnellement définir un code personnalisé :

Flight::redirect('/new/location', 301); // permanent

Arrêter l'exécution de la route

Vous pouvez arrêter le framework et sortir immédiatement à n'importe quel point en appelant la méthode halt :

Flight::halt();

Vous pouvez également spécifier un code de statut HTTP et un message optionnels :

Flight::halt(200, 'Be right back...');

Appeler halt rejettera tout contenu de réponse jusqu'à ce point et arrêtera toute exécution. Si vous voulez arrêter le framework et sortir la réponse actuelle, utilisez la méthode stop :

Flight::stop($httpStatusCode = null);

Note : Flight::stop() a un comportement étrange tel qu'il sortira la réponse mais continuera à exécuter votre script ce qui pourrait ne pas être ce que vous voulez. Vous pouvez utiliser exit ou return après avoir appelé Flight::stop() pour empêcher une exécution supplémentaire, mais il est généralement recommandé d'utiliser Flight::halt().

Cela sauvegardera la clé et la valeur de l'en-tête dans l'objet de réponse. À la fin du cycle de vie de la requête, il construira les en-têtes et enverra une réponse.

Utilisation avancée

Envoyer un en-tête immédiatement

Il peut y avoir des moments où vous devez faire quelque chose de personnalisé avec l'en-tête et vous devez envoyer l'en-tête sur cette ligne même de code avec laquelle vous travaillez. Si vous définissez une route streamée, c'est ce dont vous auriez besoin. Cela est réalisable via response()->setRealHeader().

Flight::route('/', function() {
    Flight::response()->setRealHeader('Content-Type: text/plain');
    echo 'Streaming response...';
    sleep(5);
    echo 'Done!';
})->stream();

JSONP

Pour les requêtes JSONP, vous pouvez optionnellement passer le nom du paramètre de requête que vous utilisez pour définir votre fonction de rappel :

Flight::jsonp(['id' => 123], 'q');

Donc, lors de l'envoi d'une requête GET en utilisant ?q=my_func, vous devriez recevoir la sortie :

my_func({"id":123});

Si vous ne passez pas de nom de paramètre de requête, il passera par défaut à jsonp.

Note : Si vous utilisez encore des requêtes JSONP en 2025 et au-delà, rejoignez le chat et dites-nous pourquoi ! Nous adorons entendre de bonnes histoires de batailles/horreurs !

Effacer les données de réponse

Vous pouvez effacer le corps de réponse et les en-têtes en utilisant la méthode clear(). Cela effacera tout en-tête assigné à la réponse, effacera le corps de réponse, et définira le code de statut à 200.

Flight::response()->clear();

Effacer seulement le corps de réponse

Si vous voulez seulement effacer le corps de réponse, vous pouvez utiliser la méthode clearBody() :

// Cela gardera toujours les en-têtes définis sur l'objet response().
Flight::response()->clearBody();

Mise en cache HTTP

Flight fournit un support intégré pour la mise en cache au niveau HTTP. Si la condition de mise en cache est remplie, Flight renverra une réponse HTTP 304 Not Modified. La prochaine fois que le client demandera la même ressource, il sera invité à utiliser sa version mise en cache localement.

Mise en cache au niveau de la route

Si vous voulez mettre en cache toute votre réponse, vous pouvez utiliser la méthode cache() et passer le temps de mise en cache.


// Cela mettra en cache la réponse pendant 5 minutes
Flight::route('/news', function () {
  Flight::response()->cache(time() + 300);
  echo 'This content will be cached.';
});

// Alternativement, vous pouvez utiliser une chaîne que vous passeriez
// à la méthode strtotime()
Flight::route('/news', function () {
  Flight::response()->cache('+5 minutes');
  echo 'This content will be cached.';
});

Last-Modified

Vous pouvez utiliser la méthode lastModified et passer un horodatage UNIX pour définir la date et l'heure à laquelle une page a été modifiée pour la dernière fois. Le client continuera à utiliser son cache jusqu'à ce que la valeur de dernière modification change.

Flight::route('/news', function () {
  Flight::lastModified(1234567890);
  echo 'This content will be cached.';
});

ETag

La mise en cache ETag est similaire à Last-Modified, sauf que vous pouvez spécifier n'importe quel identifiant que vous voulez pour la ressource :

Flight::route('/news', function () {
  Flight::etag('my-unique-id');
  echo 'This content will be cached.';
});

Gardez à l'esprit que l'appel à lastModified ou etag définira et vérifiera tous les deux la valeur de cache. Si la valeur de cache est la même entre les requêtes, Flight enverra immédiatement une réponse HTTP 304 et arrêtera le traitement.

Télécharger un fichier

v3.12.0

Il y a une méthode d'assistance pour streamer un fichier vers l'utilisateur final. Vous pouvez utiliser la méthode download et passer le chemin.

Flight::route('/download', function () {
  Flight::download('/path/to/file.txt');
  // À partir de v3.17.1, vous pouvez spécifier un nom de fichier personnalisé pour le téléchargement
  Flight::download('/path/to/file.txt', 'custom_name.txt');
});

Voir aussi

Dépannage

Journal des modifications

Learn/events

Gestionnaire d'événements

à partir de la v3.15.0

Aperçu

Les événements vous permettent d'enregistrer et de déclencher des comportements personnalisés dans votre application. Avec l'ajout de Flight::onEvent() et Flight::triggerEvent(), vous pouvez maintenant vous accrocher à des moments clés du cycle de vie de votre application ou définir vos propres événements (comme les notifications et les e-mails) pour rendre votre code plus modulaire et extensible. Ces méthodes font partie des méthodes mappables de Flight, ce qui signifie que vous pouvez surcharger leur comportement pour répondre à vos besoins.

Comprendre

Les événements vous permettent de séparer les différentes parties de votre application afin qu'elles ne dépendent pas trop les unes des autres. Cette séparation — souvent appelée découplage — rend votre code plus facile à mettre à jour, à étendre ou à déboguer. Au lieu d'écrire tout en un gros bloc, vous pouvez diviser votre logique en pièces plus petites et indépendantes qui répondent à des actions spécifiques (événements).

Imaginez que vous construisez une application de blog :

Sans événements, vous entasseriez tout cela dans une seule fonction. Avec les événements, vous pouvez le diviser : une partie sauvegarde le commentaire, une autre déclenche un événement comme 'comment.posted', et des écouteurs séparés gèrent l'e-mail et l'enregistrement. Cela garde votre code plus propre et vous permet d'ajouter ou de supprimer des fonctionnalités (comme les notifications) sans toucher à la logique principale.

Cas d'utilisation courants

Dans la plupart des cas, les événements sont bons pour des choses qui sont optionnelles, mais pas une partie absolument essentielle de votre système. Par exemple, les éléments suivants sont bons à avoir, mais si elles échouent pour une raison quelconque, votre application devrait toujours fonctionner :

Cependant, supposons que vous ayez une fonctionnalité de mot de passe oublié. Cela devrait faire partie de votre fonctionnalité principale et ne pas être un événement car si cet e-mail ne part pas, votre utilisateur ne peut pas réinitialiser son mot de passe et utiliser votre application.

Utilisation de base

Le système d'événements de Flight est construit autour de deux méthodes principales : Flight::onEvent() pour enregistrer les écouteurs d'événements et Flight::triggerEvent() pour déclencher les événements. Voici comment vous pouvez les utiliser :

Enregistrement des écouteurs d'événements

Pour écouter un événement, utilisez Flight::onEvent(). Cette méthode vous permet de définir ce qui doit se produire quand un événement se produit.

Flight::onEvent(string $event, callable $callback): void

Vous "souscrivez" à un événement en indiquant à Flight ce qu'il faut faire quand cela se produit. Le rappel peut accepter des arguments passés depuis le déclencheur d'événement.

Le système d'événements de Flight est synchrone, ce qui signifie que chaque écouteur d'événement est exécuté en séquence, l'un après l'autre. Quand vous déclenchez un événement, tous les écouteurs enregistrés pour cet événement s'exécuteront jusqu'à leur achèvement avant que votre code continue. Il est important de comprendre cela car cela diffère des systèmes d'événements asynchrones où les écouteurs pourraient s'exécuter en parallèle ou plus tard.

Exemple simple

Flight::onEvent('user.login', function ($username) {
    echo "Welcome back, $username!";

    // you can send an email if the login is from a new location
});

Ici, quand l'événement 'user.login' est déclenché, il saluera l'utilisateur par son nom et pourrait aussi inclure une logique pour envoyer un e-mail si nécessaire.

Note : Le rappel peut être une fonction, une fonction anonyme, ou une méthode d'une classe.

Déclenchement des événements

Pour faire se produire un événement, utilisez Flight::triggerEvent(). Cela indique à Flight d'exécuter tous les écouteurs enregistrés pour cet événement, en passant les données que vous fournissez.

Flight::triggerEvent(string $event, ...$args): void

Exemple simple

$username = 'alice';
Flight::triggerEvent('user.login', $username);

Cela déclenche l'événement 'user.login' et envoie 'alice' à l'écouteur que nous avons défini plus tôt, ce qui affichera : Welcome back, alice!.

Arrêt des événements

Si un écouteur retourne false, aucun écouteur supplémentaire pour cet événement ne sera exécuté. Cela vous permet d'arrêter la chaîne d'événements basée sur des conditions spécifiques. Souvenez-vous, l'ordre des écouteurs compte, car le premier à retourner false arrêtera les autres.

Exemple :

Flight::onEvent('user.login', function ($username) {
    if (isBanned($username)) {
        logoutUser($username);
        return false; // Stops subsequent listeners
    }
});
Flight::onEvent('user.login', function ($username) {
    sendWelcomeEmail($username); // this is never sent
});

Surcharge des méthodes d'événements

Flight::onEvent() et Flight::triggerEvent() sont disponibles pour être étendus, ce qui signifie que vous pouvez redéfinir leur fonctionnement. C'est idéal pour les utilisateurs avancés qui veulent personnaliser le système d'événements, comme ajouter de l'enregistrement ou changer la façon dont les événements sont dispatchés.

Exemple : Personnalisation de onEvent

Flight::map('onEvent', function (string $event, callable $callback) {
    // Log every event registration
    error_log("New event listener added for: $event");
    // Call the default behavior (assuming an internal event system)
    Flight::_onEvent($event, $callback);
});

Maintenant, chaque fois que vous enregistrez un événement, il l'enregistre avant de procéder.

Pourquoi surcharger ?

Où placer vos événements

Si vous êtes nouveau aux concepts d'événements dans votre projet, vous pourriez vous demander : où est-ce que j'enregistre tous ces événements dans mon application ? La simplicité de Flight signifie qu'il n'y a pas de règle stricte — vous pouvez les placer où cela a du sens pour votre projet. Cependant, les garder organisés vous aide à maintenir votre code au fur et à mesure que votre application grandit. Voici quelques options pratiques et meilleures pratiques, adaptées à la nature légère de Flight :

Option 1 : Dans votre index.php principal

Pour les petites applications ou les prototypes rapides, vous pouvez enregistrer les événements directement dans votre fichier index.php aux côtés de vos routes. Cela garde tout au même endroit, ce qui est bien quand la simplicité est votre priorité.

require 'vendor/autoload.php';

// Register events
Flight::onEvent('user.login', function ($username) {
    error_log("$username logged in at " . date('Y-m-d H:i:s'));
});

// Define routes
Flight::route('/login', function () {
    $username = 'bob';
    Flight::triggerEvent('user.login', $username);
    echo "Logged in!";
});

Flight::start();

Option 2 : Un fichier events.php séparé

Pour une application légèrement plus grande, envisagez de déplacer les enregistrements d'événements dans un fichier dédié comme app/config/events.php. Incluez ce fichier dans votre index.php avant vos routes. Cela imite la façon dont les routes sont souvent organisées dans app/config/routes.php dans les projets Flight.

// 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 : Près de l'endroit où ils sont déclenchés

Une autre approche est d'enregistrer les événements près de l'endroit où ils sont déclenchés, comme à l'intérieur d'un contrôleur ou d'une définition de route. Cela fonctionne bien si un événement est spécifique à une partie de votre application.

Flight::route('/signup', function () {
    // Register event here
    Flight::onEvent('user.registered', function ($email) {
        echo "Welcome email sent to $email!";
    });

    $email = 'jane@example.com';
    Flight::triggerEvent('user.registered', $email);
    echo "Signed up!";
});

Meilleure pratique pour Flight

Astuce : Groupez par objectif

Dans events.php, groupez les événements liés (par exemple, tous les événements liés aux utilisateurs ensemble) avec des commentaires pour plus de clarté :

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

Cette structure s'adapte bien et reste conviviale pour les débutants.

Exemples du monde réel

Parcourons quelques scénarios du monde réel pour montrer comment fonctionnent les événements et pourquoi ils sont utiles.

Exemple 1 : Enregistrement d'une connexion utilisateur

// Step 1: Register a listener
Flight::onEvent('user.login', function ($username) {
    $time = date('Y-m-d H:i:s');
    error_log("$username logged in at $time");
});

// Step 2: Trigger it in your app
Flight::route('/login', function () {
    $username = 'bob'; // Pretend this comes from a form
    Flight::triggerEvent('user.login', $username);
    echo "Hi, $username!";
});

Pourquoi c'est utile : Le code de connexion n'a pas besoin de savoir pour l'enregistrement — il déclenche juste l'événement. Vous pouvez plus tard ajouter plus d'écouteurs (par exemple, envoyer un e-mail de bienvenue) sans changer la route.

Exemple 2 : Notification de nouveaux utilisateurs

// Listener for new registrations
Flight::onEvent('user.registered', function ($email, $name) {
    // Simulate sending an email
    echo "Email sent to $email: Welcome, $name!";
});

// Trigger it when someone signs up
Flight::route('/signup', function () {
    $email = 'jane@example.com';
    $name = 'Jane';
    Flight::triggerEvent('user.registered', $email, $name);
    echo "Thanks for signing up!";
});

Pourquoi c'est utile : La logique d'inscription se concentre sur la création de l'utilisateur, tandis que l'événement gère les notifications. Vous pourriez ajouter plus d'écouteurs (par exemple, enregistrer l'inscription) plus tard.

Exemple 3 : Vider un cache

// Listener to clear a cache
Flight::onEvent('page.updated', function ($pageId) {
    // if using the flightphp/cache plugin
    Flight::cache()->delete("page_$pageId");
    echo "Cache cleared for page $pageId.";
});

// Trigger when a page is edited
Flight::route('/edit-page/(@id)', function ($pageId) {
    // Pretend we updated the page
    Flight::triggerEvent('page.updated', $pageId);
    echo "Page $pageId updated.";
});

Pourquoi c'est utile : Le code d'édition ne se soucie pas du cache — il signale juste la mise à jour. D'autres parties de l'application peuvent réagir comme nécessaire.

Meilleures pratiques

Le système d'événements dans Flight PHP, avec Flight::onEvent() et Flight::triggerEvent(), vous donne une façon simple mais puissante de construire des applications flexibles. En permettant à différentes parties de votre application de communiquer via des événements, vous pouvez garder votre code organisé, réutilisable et facile à étendre. Que vous enregistriez des actions, envoyiez des notifications ou gériez des mises à jour, les événements vous aident à le faire sans emmêler votre logique. De plus, avec la possibilité de surcharger ces méthodes, vous avez la liberté d'adapter le système à vos besoins. Commencez petit avec un seul événement, et regardez comment cela transforme la structure de votre application !

Événements intégrés

Flight PHP vient avec quelques événements intégrés que vous pouvez utiliser pour vous accrocher au cycle de vie du framework. Ces événements sont déclenchés à des points spécifiques du cycle requête/réponse, vous permettant d'exécuter une logique personnalisée quand certaines actions se produisent.

Liste des événements intégrés

Voir aussi

Dépannage

Journal des changements

Learn/templates

Vues et modèles HTML

Aperçu

Flight fournit par défaut une fonctionnalité de base de templating HTML. Le templating est une façon très efficace de déconnecter la logique de votre application de votre couche de présentation.

Compréhension

Lorsque vous construisez une application, vous aurez probablement du HTML que vous souhaiterez renvoyer à l'utilisateur final. PHP en soi est un langage de templating, mais il est très facile d'intégrer de la logique métier comme des appels à la base de données, des appels API, etc., dans votre fichier HTML, rendant le test et le découplage très difficiles. En poussant les données dans un modèle et en laissant le modèle se rendre lui-même, il devient beaucoup plus facile de découpler et de tester votre code en unités. Vous nous remercierez si vous utilisez des modèles !

Utilisation de base

Flight vous permet de remplacer le moteur de vue par défaut simplement en enregistrant votre propre classe de vue. Faites défiler vers le bas pour voir des exemples sur l'utilisation de Smarty, Latte, Blade, et plus encore !

Latte

recommandé

Voici comment utiliser le moteur de templates Latte pour vos vues.

Installation

composer require latte/latte

Configuration de base

L'idée principale est de surcharger la méthode render pour utiliser Latte au lieu du renderer PHP par défaut.

// surcharge la méthode render pour utiliser latte au lieu du renderer PHP par défaut
Flight::map('render', function(string $template, array $data, ?string $block): void {
    $latte = new Latte\Engine;

    // Où latte stocke spécifiquement son cache
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    $finalPath = Flight::get('flight.views.path') . $template;

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

Utilisation de Latte dans Flight

Maintenant que vous pouvez rendre avec Latte, vous pouvez faire quelque chose comme ceci :

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

Lorsque vous visitez /Bob dans votre navigateur, la sortie serait :

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

Lecture supplémentaire

Un exemple plus complexe d'utilisation de Latte avec des mises en page est montré dans la section awesome plugins de cette documentation.

Vous pouvez en apprendre plus sur les capacités complètes de Latte, y compris la traduction et les capacités linguistiques, en lisant la documentation officielle.

Moteur de vue intégré

déprécié

Note : Bien que cela reste la fonctionnalité par défaut et qu'elle fonctionne encore techniquement.

Pour afficher un modèle de vue, appelez la méthode render avec le nom du fichier de modèle et des données de modèle optionnelles :

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

Les données de modèle que vous passez sont automatiquement injectées dans le modèle et peuvent être référencées comme une variable locale. Les fichiers de modèle sont simplement des fichiers PHP. Si le contenu du fichier de modèle hello.php est :

Hello, <?= $name ?>!

La sortie serait :

Hello, Bob!

Vous pouvez également définir manuellement des variables de vue en utilisant la méthode set :

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

La variable name est maintenant disponible dans toutes vos vues. Vous pouvez donc simplement faire :

Flight::render('hello');

Notez que lorsque vous spécifiez le nom du modèle dans la méthode render, vous pouvez omettre l'extension .php.

Par défaut, Flight cherchera un répertoire views pour les fichiers de modèle. Vous pouvez définir un chemin alternatif pour vos modèles en configurant ce qui suit :

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

Mises en page

Il est courant pour les sites web d'avoir un seul fichier de modèle de mise en page avec un contenu interchangeable. Pour rendre du contenu à utiliser dans une mise en page, vous pouvez passer un paramètre optionnel à la méthode render.

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

Votre vue aura alors des variables sauvegardées appelées headerContent et bodyContent. Vous pouvez ensuite rendre votre mise en page en faisant :

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

Si les fichiers de modèle ressemblent à ceci :

header.php :

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

body.php :

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

layout.php :

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

La sortie serait :

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

Smarty

Voici comment utiliser le moteur de templates Smarty pour vos vues :

// Charge la bibliothèque Smarty
require './Smarty/libs/Smarty.class.php';

// Enregistre Smarty comme classe de vue
// Passe également une fonction de callback pour configurer Smarty au chargement
Flight::register('view', Smarty::class, [], function (Smarty $smarty) {
  $smarty->setTemplateDir('./templates/');
  $smarty->setCompileDir('./templates_c/');
  $smarty->setConfigDir('./config/');
  $smarty->setCacheDir('./cache/');
});

// Assigne les données de modèle
Flight::view()->assign('name', 'Bob');

// Affiche le modèle
Flight::view()->display('hello.tpl');

Pour plus de complétude, vous devriez également surcharger la méthode render par défaut de Flight :

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

Blade

Voici comment utiliser le moteur de templates Blade pour vos vues :

D'abord, vous devez installer la bibliothèque BladeOne via Composer :

composer require eftec/bladeone

Ensuite, vous pouvez configurer BladeOne comme classe de vue dans Flight :

<?php
// Charge la bibliothèque BladeOne
use eftec\bladeone\BladeOne;

// Enregistre BladeOne comme classe de vue
// Passe également une fonction de callback pour configurer BladeOne au chargement
Flight::register('view', BladeOne::class, [], function (BladeOne $blade) {
  $views = __DIR__ . '/../views';
  $cache = __DIR__ . '/../cache';

  $blade->setPath($views);
  $blade->setCompiledPath($cache);
});

// Assigne les données de modèle
Flight::view()->share('name', 'Bob');

// Affiche le modèle
echo Flight::view()->run('hello', []);

Pour plus de complétude, vous devriez également surcharger la méthode render par défaut de Flight :

<?php
Flight::map('render', function(string $template, array $data): void {
  echo Flight::view()->run($template, $data);
});

Dans cet exemple, le fichier de modèle hello.blade.php pourrait ressembler à ceci :

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

La sortie serait :

Hello, Bob!

Voir aussi

Dépannage

Changelog

Learn/collections

Collections

Aperçu

La classe Collection dans Flight est un utilitaire pratique pour gérer des ensembles de données. Elle vous permet d'accéder et de manipuler les données en utilisant à la fois la notation tableau et la notation objet, rendant votre code plus propre et plus flexible.

Compréhension

Une Collection est essentiellement un wrapper autour d'un tableau, mais avec des pouvoirs supplémentaires. Vous pouvez l'utiliser comme un tableau, itérer dessus, compter ses éléments, et même accéder aux éléments comme s'ils étaient des propriétés d'objet. Cela est particulièrement utile lorsque vous souhaitez passer des données structurées dans votre application, ou lorsque vous voulez rendre votre code un peu plus lisible.

Les Collections implémentent plusieurs interfaces PHP :

Utilisation de base

Créer une Collection

Vous pouvez créer une collection en passant simplement un tableau à son constructeur :

use flight\util\Collection;

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

$collection = new Collection($data);

Accéder aux éléments

Vous pouvez accéder aux éléments en utilisant soit la notation tableau, soit la notation objet :

// Notation tableau
echo $collection['name']; // Sortie : FlightPHP

// Notation objet
echo $collection->version; // Sortie : 3

Si vous essayez d'accéder à une clé qui n'existe pas, vous obtiendrez null au lieu d'une erreur.

Définir des éléments

Vous pouvez définir des éléments en utilisant l'une ou l'autre notation :

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

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

Vérifier et supprimer des éléments

Vérifiez si un élément existe :

if (isset($collection['name'])) {
  // Faites quelque chose
}

if (isset($collection->version)) {
  // Faites quelque chose
}

Supprimez un élément :

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

Itérer sur une Collection

Les Collections sont itérables, vous pouvez donc les utiliser dans une boucle foreach :

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

Compter les éléments

Vous pouvez compter le nombre d'éléments dans une collection :

echo count($collection); // Sortie : 4

Obtenir toutes les clés ou toutes les données

Obtenez toutes les clés :

$keys = $collection->keys(); // ['name', 'version', 'features', 'license']

Obtenez toutes les données sous forme de tableau :

$data = $collection->getData();

Vider la Collection

Supprimez tous les éléments :

$collection->clear();

Sérialisation JSON

Les Collections peuvent être facilement converties en JSON :

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

Utilisation avancée

Vous pouvez remplacer entièrement le tableau de données interne si nécessaire :

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

Les Collections sont particulièrement utiles lorsque vous souhaitez passer des données structurées entre les composants, ou lorsque vous voulez fournir une interface plus orientée objet pour les données de tableau.

Voir aussi

Dépannage

Journal des modifications

Learn/flight_vs_fat_free

Flight vs Fat-Free

Qu'est-ce que Fat-Free ?

Fat-Free (affectueusement connu sous le nom de F3) est un micro-framework PHP puissant mais facile à utiliser, conçu pour vous aider à créer des applications web dynamiques et robustes - rapidement !

Flight se compare à Fat-Free de nombreuses manières et est probablement le cousin le plus proche en termes de fonctionnalités et de simplicité. Fat-Free possède beaucoup de fonctionnalités que Flight n'a pas, mais il a aussi beaucoup de fonctionnalités que Flight possède. Fat-Free commence à montrer son âge et n'est plus aussi populaire qu'avant.

Les mises à jour deviennent moins fréquentes et la communauté n'est plus aussi active qu'autrefois. Le code est suffisamment simple, mais parfois le manque de discipline syntaxique peut le rendre difficile à lire et à comprendre. Il fonctionne pour PHP 8.3, mais le code lui-même ressemble encore à celui de PHP 5.3.

Avantages par rapport à Flight

Inconvénients par rapport à Flight

Learn/extending

Extension

Aperçu

Flight est conçu pour être un framework extensible. Le framework est livré avec un ensemble de méthodes et de composants par défaut, mais il vous permet de mapper vos propres méthodes, d'enregistrer vos propres classes, ou même de surcharger les classes et méthodes existantes.

Compréhension

Il existe 2 façons d'étendre la fonctionnalité de Flight :

  1. Mappage de méthodes - Cela est utilisé pour créer des méthodes personnalisées simples que vous pouvez appeler depuis n'importe où dans votre application. Elles sont généralement utilisées pour des fonctions utilitaires que vous souhaitez pouvoir appeler depuis n'importe où dans votre code.
  2. Enregistrement de classes - Cela est utilisé pour enregistrer vos propres classes avec Flight. Cela est généralement utilisé pour des classes qui ont des dépendances ou qui nécessitent une configuration.

Vous pouvez également surcharger les méthodes du framework existantes pour modifier leur comportement par défaut afin de mieux répondre aux besoins de votre projet.

Si vous recherchez un DIC (Dependency Injection Container), passez à la page Dependency Injection Container.

Utilisation de base

Surcharge des méthodes du framework

Flight vous permet de surcharger sa fonctionnalité par défaut pour répondre à vos propres besoins, sans avoir à modifier le code. Vous pouvez voir toutes les méthodes que vous pouvez surcharger ci-dessous.

Par exemple, lorsque Flight ne peut pas faire correspondre une URL à une route, il invoque la méthode notFound qui envoie une réponse générique HTTP 404. Vous pouvez surcharger ce comportement en utilisant la méthode map :

Flight::map('notFound', function() {
  // Afficher une page 404 personnalisée
  include 'errors/404.html';
});

Flight vous permet également de remplacer les composants principaux du framework. Par exemple, vous pouvez remplacer la classe Router par défaut par votre propre classe personnalisée :

// créer votre classe Router personnalisée
class MyRouter extends \flight\net\Router {
    // surcharger les méthodes ici
    // par exemple, un raccourci pour les requêtes GET pour supprimer
    // la fonctionnalité de passage de route
    public function get($pattern, $callback, $alias = '') {
        return parent::get($pattern, $callback, false, $alias);
    }
}

// Enregistrer votre classe personnalisée
Flight::register('router', MyRouter::class);

// Lorsque Flight charge l'instance Router, il chargera votre classe
$myRouter = Flight::router();
$myRouter->get('/hello', function() {
  echo "Hello World!";
}, 'hello_alias');

Cependant, les méthodes du framework comme map et register ne peuvent pas être surchargées. Vous obtiendrez une erreur si vous essayez de le faire (voir encore ci-dessous pour une liste des méthodes).

Méthodes du framework mappables

Voici l'ensemble complet des méthodes pour le framework. Il se compose de méthodes principales, qui sont des méthodes statiques régulières, et de méthodes extensibles, qui sont des méthodes mappées qui peuvent être filtrées ou surchargées.

Méthodes principales

Ces méthodes sont essentielles au framework et ne peuvent pas être surchargées.

Flight::map(string $name, callable $callback, bool $pass_route = false) // Crée une méthode personnalisée du framework.
Flight::register(string $name, string $class, array $params = [], ?callable $callback = null) // Enregistre une classe à une méthode du framework.
Flight::unregister(string $name) // Désenregistre une classe d'une méthode du framework.
Flight::before(string $name, callable $callback) // Ajoute un filtre avant une méthode du framework.
Flight::after(string $name, callable $callback) // Ajoute un filtre après une méthode du framework.
Flight::path(string $path) // Ajoute un chemin pour l'autoloading des classes.
Flight::get(string $key) // Obtient une variable définie par Flight::set().
Flight::set(string $key, mixed $value) // Définit une variable dans le moteur Flight.
Flight::has(string $key) // Vérifie si une variable est définie.
Flight::clear(array|string $key = []) // Efface une variable.
Flight::init() // Initialise le framework à ses paramètres par défaut.
Flight::app() // Obtient l'instance de l'objet application
Flight::request() // Obtient l'instance de l'objet requête
Flight::response() // Obtient l'instance de l'objet réponse
Flight::router() // Obtient l'instance de l'objet routeur
Flight::view() // Obtient l'instance de l'objet vue

Méthodes extensibles

Flight::start() // Démarre le framework.
Flight::stop() // Arrête le framework et envoie une réponse.
Flight::halt(int $code = 200, string $message = '') // Arrête le framework avec un code de statut et un message optionnels.
Flight::route(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mappe un motif d'URL à un callback.
Flight::post(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mappe un motif d'URL de requête POST à un callback.
Flight::put(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mappe un motif d'URL de requête PUT à un callback.
Flight::patch(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mappe un motif d'URL de requête PATCH à un callback.
Flight::delete(string $pattern, callable $callback, bool $pass_route = false, string $alias = '') // Mappe un motif d'URL de requête DELETE à un callback.
Flight::group(string $pattern, callable $callback) // Crée un groupement pour les URLs, le motif doit être une chaîne.
Flight::getUrl(string $name, array $params = []) // Génère une URL basée sur un alias de route.
Flight::redirect(string $url, int $code) // Redirige vers une autre URL.
Flight::download(string $filePath) // Télécharge un fichier.
Flight::render(string $file, array $data, ?string $key = null) // Rend un fichier de template.
Flight::error(Throwable $error) // Envoie une réponse HTTP 500.
Flight::notFound() // Envoie une réponse HTTP 404.
Flight::etag(string $id, string $type = 'string') // Effectue le cache HTTP ETag.
Flight::lastModified(int $time) // Effectue le cache HTTP de dernière modification.
Flight::json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envoie une réponse JSON.
Flight::jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envoie une réponse JSONP.
Flight::jsonHalt(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf8', int $option) // Envoie une réponse JSON et arrête le framework.
Flight::onEvent(string $event, callable $callback) // Enregistre un écouteur d'événement.
Flight::triggerEvent(string $event, ...$args) // Déclenche un événement.

Toute méthode personnalisée ajoutée avec map et register peut également être filtrée. Pour des exemples sur la façon de filtrer ces méthodes, voir le guide Filtering Methods.

Classes du framework extensibles

Il existe plusieurs classes dont vous pouvez surcharger la fonctionnalité en les étendant et en enregistrant votre propre classe. Ces classes sont :

Flight::app() // Classe Application - étendre la classe flight\Engine
Flight::request() // Classe Requête - étendre la classe flight\net\Request
Flight::response() // Classe Réponse - étendre la classe flight\net\Response
Flight::router() // Classe Routeur - étendre la classe flight\net\Router
Flight::view() // Classe Vue - étendre la classe flight\template\View
Flight::eventDispatcher() // Classe Dispatch d'événements - étendre la classe flight\core\Dispatcher

Mappage de méthodes personnalisées

Pour mapper votre propre méthode personnalisée simple, vous utilisez la fonction map :

// Mapper votre méthode
Flight::map('hello', function (string $name) {
  echo "hello $name!";
});

// Appeler votre méthode personnalisée
Flight::hello('Bob');

Bien qu'il soit possible de créer des méthodes personnalisées simples, il est recommandé de simplement créer des fonctions standard en PHP. Cela offre l'autocomplétion dans les IDE et est plus facile à lire. L'équivalent du code ci-dessus serait :

function hello(string $name) {
  echo "hello $name!";
}

hello('Bob');

Cela est utilisé plus souvent lorsque vous devez passer des variables dans votre méthode pour obtenir une valeur attendue. Utiliser la méthode register() comme ci-dessous est plus pour passer une configuration et ensuite appeler votre classe pré-configurée.

Enregistrement de classes personnalisées

Pour enregistrer votre propre classe et la configurer, vous utilisez la fonction register. L'avantage que cela a sur map() est que vous pouvez réutiliser la même classe lorsque vous appelez cette fonction (ce qui serait utile avec Flight::db() pour partager la même instance).

// Enregistrer votre classe
Flight::register('user', User::class);

// Obtenir une instance de votre classe
$user = Flight::user();

La méthode register vous permet également de passer des paramètres au constructeur de votre classe. Ainsi, lorsque vous chargez votre classe personnalisée, elle sera pré-initialisée. Vous pouvez définir les paramètres du constructeur en passant un tableau supplémentaire. Voici un exemple de chargement d'une connexion à la base de données :

// Enregistrer la classe avec des paramètres de constructeur
Flight::register('db', PDO::class, ['mysql:host=localhost;dbname=test', 'user', 'pass']);

// Obtenir une instance de votre classe
// Cela créera un objet avec les paramètres définis
//
// new PDO('mysql:host=localhost;dbname=test','user','pass');
//
$db = Flight::db();

// et si vous en avez besoin plus tard dans votre code, vous appelez simplement la même méthode
class SomeController {
  public function __construct() {
    $this->db = Flight::db();
  }
}

Si vous passez un paramètre de callback supplémentaire, il sera exécuté immédiatement après la construction de la classe. Cela vous permet d'effectuer toute procédure de configuration pour votre nouvel objet. La fonction de callback prend un paramètre, une instance du nouvel objet.

// Le callback recevra l'objet qui a été construit
Flight::register(
  'db',
  PDO::class,
  ['mysql:host=localhost;dbname=test', 'user', 'pass'],
  function (PDO $db) {
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  }
);

Par défaut, chaque fois que vous chargez votre classe, vous obtiendrez une instance partagée. Pour obtenir une nouvelle instance d'une classe, passez simplement false en tant que paramètre :

// Instance partagée de la classe
$shared = Flight::db();

// Nouvelle instance de la classe
$new = Flight::db(false);

Note : Gardez à l'esprit que les méthodes mappées ont la priorité sur les classes enregistrées. Si vous déclarez les deux en utilisant le même nom, seule la méthode mappée sera invoquée.

Exemples

Voici quelques exemples de la façon dont vous pouvez étendre Flight avec une fonctionnalité qui n'est pas intégrée au noyau.

Journalisation

Flight n'a pas de système de journalisation intégré, cependant, il est vraiment facile d'utiliser une bibliothèque de journalisation avec Flight. Voici un exemple utilisant la bibliothèque Monolog :

// services.php

// Enregistrer le logger avec 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));
});

Maintenant qu'il est enregistré, vous pouvez l'utiliser dans votre application :

// Dans votre contrôleur ou route
Flight::log()->warning('This is a warning message');

Cela journalisera un message dans le fichier de log que vous avez spécifié. Et si vous voulez journaliser quelque chose quand une erreur se produit ? Vous pouvez utiliser la méthode error :

// Dans votre contrôleur ou route
Flight::map('error', function(Throwable $ex) {
    Flight::log()->error($ex->getMessage());
    // Afficher votre page d'erreur personnalisée
    include 'errors/500.html';
});

Vous pourriez également créer un système APM (Application Performance Monitoring) de base en utilisant les méthodes before et after :

// Dans votre fichier services.php

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

    // Vous pourriez également ajouter vos en-têtes de requête ou de réponse
    // pour les journaliser également (soyez prudent car cela serait beaucoup de 
    // données si vous avez beaucoup de requêtes)
    Flight::log()->info('Request Headers: ' . json_encode(Flight::request()->headers));
    Flight::log()->info('Response Headers: ' . json_encode(Flight::response()->headers));
});

Mise en cache

Flight n'a pas de système de mise en cache intégré, cependant, il est vraiment facile d'utiliser une bibliothèque de mise en cache avec Flight. Voici un exemple utilisant la bibliothèque PHP File Cache :

// services.php

// Enregistrer le cache avec Flight
Flight::register('cache', \flight\Cache::class, [ __DIR__ . '/../cache/' ], function(\flight\Cache $cache) {
    $cache->setDevMode(ENVIRONMENT === 'development');
});

Maintenant qu'il est enregistré, vous pouvez l'utiliser dans votre application :

// Dans votre contrôleur ou route
$data = Flight::cache()->get('my_cache_key');
if (empty($data)) {
    // Effectuer un traitement pour obtenir les données
    $data = [ 'some' => 'data' ];
    Flight::cache()->set('my_cache_key', $data, 3600); // cache pour 1 heure
}

Instanciation facile d'objets DIC

Si vous utilisez un DIC (Dependency Injection Container) dans votre application, vous pouvez utiliser Flight pour vous aider à instancier vos objets. Voici un exemple utilisant la bibliothèque Dice :

// services.php

// créer un nouveau conteneur
$container = new \Dice\Dice;
// n'oubliez pas de le réassigner à lui-même comme ci-dessous !
$container = $container->addRule('PDO', [
    // shared signifie que le même objet sera retourné à chaque fois
    'shared' => true,
    'constructParams' => ['mysql:host=localhost;dbname=test', 'user', 'pass' ]
]);

// maintenant nous pouvons créer une méthode mappable pour créer n'importe quel objet. 
Flight::map('make', function($class, $params = []) use ($container) {
    return $container->create($class, $params);
});

// Cela enregistre le gestionnaire de conteneur pour que Flight sache l'utiliser pour les contrôleurs/middleware
Flight::registerContainerHandler(function($class, $params) {
    Flight::make($class, $params);
});

// disons que nous avons la classe d'exemple suivante qui prend un objet PDO dans le constructeur
class EmailCron {
    protected PDO $pdo;

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

    public function send() {
        // code qui envoie un email
    }
}

// Et enfin vous pouvez créer des objets en utilisant l'injection de dépendances
$emailCron = Flight::make(EmailCron::class);
$emailCron->send();

Génial, non ?

Voir aussi

Dépannage

Journal des modifications

Learn/json

Wrapper JSON

Aperçu

La classe Json dans Flight fournit une façon simple et cohérente d'encoder et de décoder des données JSON dans votre application. Elle enveloppe les fonctions JSON natives de PHP avec une meilleure gestion des erreurs et certains paramètres par défaut utiles, rendant plus facile et plus sûr de travailler avec JSON.

Comprendre

Travailler avec JSON est très courant dans les applications PHP modernes, surtout lors de la construction d'API ou de la gestion de requêtes AJAX. La classe Json centralise tout l'encodage et le décodage JSON, afin que vous n'ayez pas à vous soucier de cas limites étranges ou d'erreurs cryptiques des fonctions intégrées de PHP.

Fonctionnalités clés :

Utilisation de base

Encodage des données en JSON

Pour convertir des données PHP en une chaîne JSON, utilisez Json::encode() :

use flight\util\Json;

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

$json = Json::encode($data);
echo $json;
// Output: {"framework":"Flight","version":3,"features":["routing","views","extending"]}

Si l'encodage échoue, vous obtiendrez une exception avec un message d'erreur utile.

Impression formatée

Voulez-vous que votre JSON soit lisible par un humain ? Utilisez prettyPrint() :

echo Json::prettyPrint($data);
/*
{
  "framework": "Flight",
  "version": 3,
  "features": [
    "routing",
    "views",
    "extending"
  ]
}
*/

Décodage de chaînes JSON

Pour convertir une chaîne JSON en données PHP, utilisez Json::decode() :

$json = '{"framework":"Flight","version":3}';
$data = Json::decode($json);
echo $data->framework; // Output: Flight

Si vous voulez un tableau associatif au lieu d'un objet, passez true comme second argument :

$data = Json::decode($json, true);
echo $data['framework']; // Output: Flight

Si le décodage échoue, vous obtiendrez une exception avec un message d'erreur clair.

Validation de JSON

Vérifiez si une chaîne est un JSON valide :

if (Json::isValid($json)) {
  // C'est valide !
} else {
  // Pas un JSON valide
}

Obtenir la dernière erreur

Si vous voulez vérifier le dernier message d'erreur JSON (des fonctions PHP natives) :

$error = Json::getLastError();
if ($error !== '') {
  echo "Last JSON error: $error";
}

Utilisation avancée

Vous pouvez personnaliser les options d'encodage et de décodage si vous avez besoin de plus de contrôle (voir les options de json_encode de PHP) :

// Encoder avec l'option JSON_HEX_TAG
$json = Json::encode($data, JSON_HEX_TAG);

// Décoder avec une profondeur personnalisée
$data = Json::decode($json, false, 1024);

Voir aussi

Dépannage

Journal des modifications

Learn/flight_vs_slim

Flight vs Slim

Qu'est-ce que Slim ?

Slim est un micro-framework PHP qui vous aide à écrire rapidement des applications web simples mais puissantes et des API.

Beaucoup d'inspirations pour certaines fonctionnalités de la version 3 de Flight proviennent en fait de Slim. La regroupement des routes et l'exécution du middleware dans un ordre spécifique sont deux fonctionnalités inspirées de Slim. Slim v3 est sorti avec un accent sur la simplicité, mais il y a eu des avis mitigés concernant la v4.

Avantages par rapport à Flight

Inconvénients par rapport à Flight

Learn/autoloading

Autoloading

Aperçu

L'autoloading est un concept en PHP où vous spécifiez un répertoire ou des répertoires pour charger les classes. Cela est beaucoup plus avantageux que d'utiliser require ou include pour charger les classes. C'est également une exigence pour utiliser les packages Composer.

Compréhension

Par défaut, toute classe Flight est autoloadée automatiquement pour vous grâce à Composer. Cependant, si vous souhaitez autoloader vos propres classes, vous pouvez utiliser la méthode Flight::path() pour spécifier un répertoire à partir duquel charger les classes.

L'utilisation d'un autoloader peut aider à simplifier votre code de manière significative. Au lieu d'avoir des fichiers qui commencent par une multitude d'instructions include ou require en haut pour capturer toutes les classes utilisées dans ce fichier, vous pouvez au contraire appeler dynamiquement vos classes et elles seront incluses automatiquement.

Utilisation de base

Supposons que nous ayons un arbre de répertoires comme suit :

# Exemple de chemin
/home/user/project/my-flight-project/
├── app
│   ├── cache
│   ├── config
│   ├── controllers - contient les contrôleurs pour ce projet
│   ├── translations
│   ├── UTILS - contient les classes pour cette application uniquement (tout en majuscules exprès pour un exemple plus tard)
│   └── views
└── public
    └── css
    └── js
    └── index.php

Vous avez peut-être remarqué que c'est la même structure de fichiers que celle de ce site de documentation.

Vous pouvez spécifier chaque répertoire à charger comme ceci :


/**
 * public/index.php
 */

// Ajouter un chemin à l'autoloader
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');

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

// pas de namespace requis

// Toutes les classes autoloadées sont recommandées d'être en Pascal Case (chaque mot en majuscule, sans espaces)
class MyController {

    public function index() {
        // faire quelque chose
    }
}

Espaces de noms

Si vous avez des namespaces, il devient en fait très facile de les implémenter. Vous devriez utiliser la méthode Flight::path() pour spécifier le répertoire racine (pas la racine du document ou le dossier public/) de votre application.


/**
 * public/index.php
 */

// Ajouter un chemin à l'autoloader
Flight::path(__DIR__.'/../');

Maintenant, voici à quoi pourrait ressembler votre contrôleur. Regardez l'exemple ci-dessous, mais prêtez attention aux commentaires pour des informations importantes.

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

// les namespaces sont requis
// les namespaces sont les mêmes que la structure de répertoires
// les namespaces doivent suivre la même casse que la structure de répertoires
// les namespaces et les répertoires ne peuvent pas avoir de tirets bas (sauf si Loader::setV2ClassLoading(false) est défini)
namespace app\controllers;

// Toutes les classes autoloadées sont recommandées d'être en Pascal Case (chaque mot en majuscule, sans espaces)
// À partir de 3.7.2, vous pouvez utiliser Pascal_Snake_Case pour les noms de vos classes en exécutant Loader::setV2ClassLoading(false);
class MyController {

    public function index() {
        // faire quelque chose
    }
}

Et si vous vouliez autoloader une classe dans votre répertoire utils, vous feriez essentiellement la même chose :


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

// le namespace doit correspondre à la structure de répertoires et à la casse (notez que le répertoire UTILS est tout en majuscules
//     comme dans l'arbre de fichiers ci-dessus)
namespace app\UTILS;

class ArrayHelperUtil {

    public function changeArrayCase(array $array) {
        // faire quelque chose
    }
}

Tirets bas dans les noms de classes

À partir de 3.7.2, vous pouvez utiliser Pascal_Snake_Case pour les noms de vos classes en exécutant Loader::setV2ClassLoading(false);. Cela vous permettra d'utiliser des tirets bas dans les noms de vos classes. Cela n'est pas recommandé, mais c'est disponible pour ceux qui en ont besoin.

use flight\core\Loader;

/**
 * public/index.php
 */

// Ajouter un chemin à l'autoloader
Flight::path(__DIR__.'/../app/controllers/');
Flight::path(__DIR__.'/../app/utils/');
Loader::setV2ClassLoading(false);

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

// pas de namespace requis

class My_Controller {

    public function index() {
        // faire quelque chose
    }
}

Voir aussi

Dépannage

Classe non trouvée (autoloading ne fonctionne pas)

Il pourrait y avoir plusieurs raisons pour cela. Ci-dessous, quelques exemples, mais assurez-vous également de consulter la section autoloading.

Nom de fichier incorrect

Le plus courant est que le nom de la classe ne correspond pas au nom du fichier.

Si vous avez une classe nommée MyClass, alors le fichier devrait s'appeler MyClass.php. Si vous avez une classe nommée MyClass et que le fichier s'appelle myclass.php alors l'autoloader ne pourra pas la trouver.

Namespace incorrect

Si vous utilisez des namespaces, alors le namespace devrait correspondre à la structure de répertoires.

// ...code...

// si votre MyController est dans le répertoire app/controllers et qu'il est namespacé
// cela ne fonctionnera pas.
Flight::route('/hello', 'MyController->hello');

// vous devrez choisir l'une de ces options
Flight::route('/hello', 'app\controllers\MyController->hello');
// ou si vous avez une instruction use en haut

use app\controllers\MyController;

Flight::route('/hello', [ MyController::class, 'hello' ]);
// peut aussi être écrit
Flight::route('/hello', MyController::class.'->hello');
// ou encore...
Flight::route('/hello', [ 'app\controllers\MyController', 'hello' ]);

path() non défini

Dans l'application squelette, cela est défini dans le fichier config.php, mais pour que vos classes soient trouvées, vous devez vous assurer que la méthode path() est définie (probablement vers la racine de votre répertoire) avant de l'utiliser.

// Ajouter un chemin à l'autoloader
Flight::path(__DIR__.'/../');

Journal des modifications

Learn/uploaded_file

Gestionnaire de Fichiers Téléversés

Aperçu

La classe UploadedFile dans Flight facilite et sécurise la gestion des téléversements de fichiers dans votre application. Elle encapsule les détails du processus de téléversement de fichiers de PHP, vous offrant une façon simple et orientée objet d'accéder aux informations sur les fichiers et de déplacer les fichiers téléversés.

Compréhension

Lorsque un utilisateur téléverse un fichier via un formulaire, PHP stocke les informations sur le fichier dans le superglobal $_FILES. Dans Flight, vous interagissez rarement directement avec $_FILES. Au lieu de cela, l'objet Request de Flight (accessible via Flight::request()) fournit une méthode getUploadedFiles() qui retourne un tableau d'objets UploadedFile, rendant la gestion des fichiers beaucoup plus pratique et robuste.

La classe UploadedFile fournit des méthodes pour :

Cette classe vous aide à éviter les pièges courants avec les téléversements de fichiers, comme la gestion des erreurs ou le déplacement sécurisé des fichiers.

Utilisation de Base

Accès aux Fichiers Téléversés depuis une Requête

La façon recommandée d'accéder aux fichiers téléversés est via l'objet de requête :

Flight::route('POST /upload', function() {
    // Pour un champ de formulaire nommé <input type="file" name="myFile">
    $uploadedFiles = Flight::request()->getUploadedFiles();
    $file = $uploadedFiles['myFile'];

    // Maintenant vous pouvez utiliser les méthodes de UploadedFile
    if ($file->getError() === UPLOAD_ERR_OK) {
        $file->moveTo('/path/to/uploads/' . $file->getClientFilename());
        echo "Fichier téléversé avec succès !";
    } else {
        echo "Échec du téléversement : " . $file->getError();
    }
});

Gestion de Téléversements Multiples de Fichiers

Si votre formulaire utilise name="myFiles[]" pour des téléversements multiples, vous obtiendrez un tableau d'objets UploadedFile :

Flight::route('POST /upload', function() {
    // Pour un champ de formulaire nommé <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 "Téléversé : " . $file->getClientFilename() . "<br>";
        } else {
            echo "Échec du téléversement : " . $file->getClientFilename() . "<br>";
        }
    }
});

Création Manuelle d'une Instance UploadedFile

Généralement, vous ne créerez pas un UploadedFile manuellement, mais vous le pouvez si nécessaire :

use flight\net\UploadedFile;

$file = new UploadedFile(
  $_FILES['myfile']['name'],
  $_FILES['myfile']['type'],
  $_FILES['myfile']['size'],
  $_FILES['myfile']['tmp_name'],
  $_FILES['myfile']['error']
);

Accès aux Informations sur le Fichier

Vous pouvez facilement obtenir les détails sur le fichier téléversé :

echo $file->getClientFilename();   // Nom de fichier original depuis l'ordinateur de l'utilisateur
echo $file->getClientMediaType();  // Type MIME (par ex., image/png)
echo $file->getSize();             // Taille du fichier en octets
echo $file->getTempName();         // Chemin temporaire du fichier sur le serveur
echo $file->getError();            // Code d'erreur de téléversement (0 signifie pas d'erreur)

Déplacement du Fichier Téléversé

Après avoir validé le fichier, déplacez-le vers un emplacement permanent :

try {
  $file->moveTo('/path/to/uploads/' . $file->getClientFilename());
  echo "Fichier téléversé avec succès !";
} catch (Exception $e) {
  echo "Échec du téléversement : " . $e->getMessage();
}

La méthode moveTo() lancera une exception si quelque chose se passe mal (comme une erreur de téléversement ou un problème de permissions).

Gestion des Erreurs de Téléversement

S'il y a eu un problème pendant le téléversement, vous pouvez obtenir un message d'erreur lisible par un humain :

if ($file->getError() !== UPLOAD_ERR_OK) {
  // Vous pouvez utiliser le code d'erreur ou capturer l'exception de moveTo()
  echo "Il y a eu une erreur lors du téléversement du fichier.";
}

Voir Aussi

Dépannage

Journal des Modifications

Guides/unit_testing

Tests unitaires dans Flight PHP avec PHPUnit

Ce guide présente les tests unitaires dans Flight PHP en utilisant PHPUnit, destiné aux débutants qui souhaitent comprendre pourquoi les tests unitaires sont importants et comment les appliquer de manière pratique. Nous nous concentrerons sur les tests de comportement — s'assurer que votre application fait ce que vous attendez, comme envoyer un e-mail ou sauvegarder un enregistrement — plutôt que sur des calculs triviaux. Nous commencerons par un simple gestionnaire de routes et progresserons vers un contrôleur plus complexe, en intégrant l'injection de dépendances [/learn/dependency-injection-container) (DI) et la simulation de services tiers.

Pourquoi effectuer des tests unitaires ?

Les tests unitaires garantissent que votre code se comporte comme prévu, en détectant les bogues avant qu'ils n'atteignent la production. Ils sont particulièrement précieux dans Flight, où le routage léger et la flexibilité peuvent entraîner des interactions complexes. Pour les développeurs solos ou les équipes, les tests unitaires servent de filet de sécurité, documentent le comportement attendu et préviennent les régressions lorsque vous revenez sur le code plus tard. Ils améliorent également la conception : un code difficile à tester signale souvent des classes trop complexes ou trop fortement couplées.

Contrairement à des exemples simplistes (par exemple, tester x * y = z), nous nous concentrerons sur des comportements du monde réel, tels que la validation d'entrée, la sauvegarde de données ou le déclenchement d'actions comme les e-mails. Notre objectif est de rendre les tests accessibles et significatifs.

Principes directeurs généraux

  1. Tester le comportement, pas l'implémentation : Concentrez-vous sur les résultats (par exemple, « e-mail envoyé » ou « enregistrement sauvegardé ») plutôt que sur les détails internes. Cela rend les tests robustes face aux refactorisations.
  2. Arrêtez d'utiliser Flight:: : Les méthodes statiques de Flight sont terriblement pratiques, mais rendent les tests difficiles. Vous devriez vous habituer à utiliser la variable $app de $app = Flight::app();. $app possède toutes les mêmes méthodes que Flight::. Vous pourrez toujours utiliser $app->route() ou $this->app->json() dans votre contrôleur, etc. Vous devriez également utiliser le vrai routeur Flight avec $router = $app->router() et ensuite vous pouvez utiliser $router->get(), $router->post(), $router->group(), etc. Voir Routage.
  3. Gardez les tests rapides : Des tests rapides encouragent une exécution fréquente. Évitez les opérations lentes comme les appels à la base de données dans les tests unitaires. Si vous avez un test lent, c'est un signe que vous écrivez un test d'intégration, pas un test unitaire. Les tests d'intégration sont ceux où vous impliquerez réellement des bases de données réelles, des appels HTTP réels, l'envoi d'e-mails réels, etc. Ils ont leur place, mais ils sont lents et peuvent être instables, ce qui signifie qu'ils échouent parfois pour une raison inconnue.
  4. Utilisez des noms descriptifs : Les noms des tests doivent décrire clairement le comportement testé. Cela améliore la lisibilité et la maintenabilité.
  5. Évitez les globales comme la peste : Minimisez l'utilisation de $app->set() et $app->get(), car elles agissent comme un état global, nécessitant des simulations dans chaque test. Privilégiez l'injection de dépendances ou un conteneur DI (voir Conteneur d'injection de dépendances). Même l'utilisation de la méthode $app->map() est techniquement une « globale » et devrait être évitée au profit de l'injection de dépendances. Utilisez une bibliothèque de session comme flightphp/session afin de pouvoir simuler l'objet session dans vos tests. Ne appelez pas $_SESSION directement dans votre code car cela injecte une variable globale dans votre code, rendant le test difficile.
  6. Utilisez l'injection de dépendances : Injectez les dépendances (par exemple, PDO, expéditeurs d'e-mails) dans les contrôleurs pour isoler la logique et simplifier la simulation. Si vous avez une classe avec trop de dépendances, envisagez de la refactoriser en classes plus petites, chacune ayant une seule responsabilité suivant les principes SOLID.
  7. Simulez les services tiers : Simulez les bases de données, les clients HTTP (cURL) ou les services d'e-mail pour éviter les appels externes. Testez une ou deux couches en profondeur, mais laissez votre logique principale s'exécuter. Par exemple, si votre application envoie un message texte, vous NE VOULEZ PAS vraiment envoyer un message texte à chaque exécution de vos tests car ces frais s'accumuleront (et ce sera plus lent). Au lieu de cela, simulez le service de message texte et vérifiez simplement que votre code a appelé le service de message texte avec les bons paramètres.
  8. Visez une couverture élevée, pas la perfection : Une couverture de ligne à 100 % est bonne, mais cela ne signifie pas réellement que tout dans votre code est testé comme il le devrait (allez-y et recherchez la couverture de branche/chemin dans PHPUnit). Priorisez les comportements critiques (par exemple, l'inscription d'utilisateur, les réponses API et la capture des réponses échouées).
  9. Utilisez des contrôleurs pour les routes : Dans vos définitions de routes, utilisez des contrôleurs et non des fermetures. L'instance flight\Engine $app est injectée dans chaque contrôleur via le constructeur par défaut. Dans les tests, utilisez $app = new Flight\Engine() pour instancier Flight dans un test, injectez-la dans votre contrôleur et appelez directement les méthodes (par exemple, $controller->register()). Voir Extension de Flight et Routage.
  10. Choisissez un style de simulation et tenez-vous-y : PHPUnit prend en charge plusieurs styles de simulation (par exemple, prophecy, simulations intégrées), ou vous pouvez utiliser des classes anonymes qui ont leurs propres avantages comme l'autocomplétion de code, la rupture si vous changez la définition de la méthode, etc. Soyez simplement cohérent dans vos tests. Voir Objets simulés de PHPUnit.
  11. Utilisez la visibilité protected pour les méthodes/propriétés que vous voulez tester dans les sous-classes : Cela vous permet de les surcharger dans les sous-classes de test sans les rendre publiques, ce qui est particulièrement utile pour les simulations de classes anonymes.

Configuration de PHPUnit

Tout d'abord, configurez PHPUnit dans votre projet Flight PHP en utilisant Composer pour des tests faciles. Voir le guide de démarrage de PHPUnit pour plus de détails.

  1. Dans le répertoire de votre projet, exécutez :

    composer require --dev phpunit/phpunit

    Cela installe la dernière version de PHPUnit en tant que dépendance de développement.

  2. Créez un répertoire tests à la racine de votre projet pour les fichiers de test.

  3. Ajoutez un script de test à composer.json pour plus de commodité :

    // autre contenu de composer.json
    "scripts": {
       "test": "phpunit --configuration phpunit.xml"
    }
  4. Créez un fichier phpunit.xml à la racine :

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

Maintenant, lorsque vos tests sont construits, vous pouvez exécuter composer test pour exécuter les tests.

Test d'un gestionnaire de route simple

Commençons par une route de base [/learn/routing] qui valide l'entrée e-mail d'un utilisateur. Nous testerons son comportement : renvoyer un message de succès pour les e-mails valides et une erreur pour les invalides. Pour la validation d'e-mail, nous utilisons filter_var.

// index.php
$app->route('POST /register', [ UserController::class, 'register' ]);

// UserController.php
class UserController {
    protected $app;

    public function __construct(flight\Engine $app) {
        $this->app = $app;
    }

    public function register() {
        $email = $this->app->request()->data->email;
        $responseArray = [];
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $responseArray = ['status' => 'error', 'message' => 'Invalid email'];
        } else {
            $responseArray = ['status' => 'success', 'message' => 'Valid email'];
        }

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

Pour tester cela, créez un fichier de test. Voir Tests unitaires et principes SOLID pour plus d'informations sur la structuration des tests :

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

class UserControllerTest extends TestCase {

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

Points clés :

Exécutez composer test pour vérifier que la route se comporte comme prévu. Pour plus d'informations sur les requêtes et les réponses dans Flight, voir les documents pertinents.

Utilisation de l'injection de dépendances pour des contrôleurs testables

Pour des scénarios plus complexes, utilisez l'injection de dépendances [/learn/dependency-injection-container) (DI) pour rendre les contrôleurs testables. Évitez les globales de Flight (par exemple, Flight::set(), Flight::map(), Flight::register()) car elles agissent comme un état global, nécessitant des simulations pour chaque test. Au lieu de cela, utilisez le conteneur DI de Flight, DICE, PHP-DI ou une injection manuelle DI.

Utilisons flight\database\PdoWrapper au lieu de PDO brut. Ce wrapper est beaucoup plus facile à simuler et à tester unitairement !

Voici un contrôleur qui sauvegarde un utilisateur dans une base de données et envoie un e-mail de bienvenue :

use flight\database\PdoWrapper;

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' => 'Invalid email']);
        }

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

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

Points clés :

Test du contrôleur avec des simulations

Maintenant, testons le comportement de UserController : validation des e-mails, sauvegarde en base de données et envoi d'e-mails. Nous simulerons la base de données et l'expéditeur pour isoler le contrôleur.

// tests/UserControllerDICTest.php
use PHPUnit\Framework\TestCase;

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('User registered', $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('Should not be called');
            }
        };
        $mockMailer = new class implements MailerInterface {
            public $sentEmail = null;
            public function sendWelcome($email): bool {
                throw new Exception('Should not be called');
            }
        };
        $app = new Engine();
        $app->request()->data->email = 'invalid-email';

        // 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('Invalid email', $result['message']);
    }
}

Points clés :

Simulation excessive

Faites attention à ne pas simuler trop de votre code. Laissez-moi vous donner un exemple ci-dessous sur pourquoi cela pourrait être une mauvaise chose en utilisant notre UserController. Nous changerons cette vérification en une méthode appelée isEmailValid (en utilisant filter_var) et les autres ajouts en une méthode séparée appelée 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' => 'Invalid email']);
        }

        $this->registerUser($email);

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

    protected function isEmailValid($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

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

Et maintenant le test unitaire sursimulé qui ne teste en réalité rien :

use PHPUnit\Framework\TestCase;

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('User registered', $result['message']);
    }
}

Hourra, nous avons des tests unitaires et ils passent ! Mais attendez, que se passe-t-il si je change réellement le fonctionnement interne de isEmailValid ou registerUser ? Mes tests passeront toujours parce que j'ai simulé toute la fonctionnalité. Laissez-moi vous montrer ce que je veux dire.

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

Si j'exécute mes tests unitaires ci-dessus, ils passent toujours ! Mais parce que je n'ai pas testé pour le comportement (en laissant une partie du code s'exécuter réellement), j'ai potentiellement codé un bogue en attente de se produire en production. Le test devrait être modifié pour prendre en compte le nouveau comportement, et aussi l'opposé quand le comportement n'est pas ce que nous attendons.

Exemple complet

Vous pouvez trouver un exemple complet d'un projet Flight PHP avec des tests unitaires sur GitHub : n0nag0n/flight-unit-tests-guide. Pour une compréhension plus approfondie, voir Tests unitaires et principes SOLID.

Pièges courants

Évolutivité avec les tests unitaires

Les tests unitaires brillent dans les projets plus grands ou lors de la reprise de code après des mois. Ils documentent le comportement et détectent les régressions, vous évitant de réapprendre votre application. Pour les devs solos, testez les chemins critiques (par exemple, inscription utilisateur, traitement des paiements). Pour les équipes, les tests assurent un comportement cohérent à travers les contributions. Voir Pourquoi les frameworks ? pour plus d'informations sur les avantages d'utiliser des frameworks et des tests.

Contribuez vos propres conseils de test au dépôt de documentation Flight PHP !

Écrit par n0nag0n 2025

Guides/blog

Créer un blog simple avec Flight PHP

Ce guide vous accompagne pour créer un blog de base en utilisant le framework Flight PHP. Vous allez configurer un projet, définir des routes, gérer des publications avec JSON et les afficher avec le moteur de templates Latte, tout en mettant en avant la simplicité et la flexibilité de Flight. À la fin, vous aurez un blog fonctionnel avec une page d'accueil, des pages de publication individuelles et un formulaire de création.

Prérequis

Étape 1 : Configurer votre projet

Commencez par créer un nouveau répertoire de projet et installez Flight via Composer.

  1. Créer un répertoire :

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

    composer require flightphp/core
  3. Créer un répertoire public : Flight utilise un point d'entrée unique (index.php). Créez un dossier public/ pour cela :

    mkdir public
  4. Base index.php : Créez public/index.php avec une route simple « hello world » :

    <?php
    require '../vendor/autoload.php';
    
    Flight::route('/', function () {
       echo 'Bonjour, Flight !';
    });
    
    Flight::start();
  5. Exécuter le serveur intégré : Testez votre configuration avec le serveur de développement de PHP :

    php -S localhost:8000 -t public/

    Visitez http://localhost:8000 pour voir « Bonjour, Flight ! ».

Étape 2 : Organiser la structure de votre projet

Pour une configuration propre, structurez votre projet comme ceci :

flight-blog/
├── app/
│   ├── config/
│   └── views/
├── data/
├── public/
│   └── index.php
├── vendor/
└── composer.json

Étape 3 : Installer et configurer Latte

Latte est un moteur de templates léger qui s'intègre bien avec Flight.

  1. Installer Latte :

    composer require latte/latte
  2. Configurer Latte dans Flight : Mettez à jour public/index.php pour enregistrer Latte comme moteur de vues :

    <?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' => 'Mon Blog']);
    });
    
    Flight::start();
  3. Créer un template de mise en page : Dans app/views/layout.latte :

    <!DOCTYPE html>
    <html>
    <head>
    <title>{$title}</title>
    </head>
    <body>
    <header>
        <h1>Mon Blog</h1>
        <nav>
            <a href="/">Accueil</a> | 
            <a href="/create">Créer un Article</a>
        </nav>
    </header>
    <main>
        {block content}{/block}
    </main>
    <footer>
        <p>&copy; {date('Y')} Blog Flight</p>
    </footer>
    </body>
    </html>
  4. Créer un template d'accueil : Dans 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}

    Redémarrez le serveur si vous en êtes sorti et visitez http://localhost:8000 pour voir la page rendue.

  5. Créer un fichier de données :

    Utilisez un fichier JSON pour simuler une base de données pour plus de simplicité.

    Dans data/posts.json :

    [
       {
           "slug": "premier-article",
           "title": "Mon Premier Article",
           "content": "Ceci est mon tout premier article de blog avec Flight PHP !"
       }
    ]

Étape 4 : Définir les routes

Séparez vos routes dans un fichier de configuration pour une meilleure organisation.

  1. Créer routes.php : Dans app/config/routes.php :

    <?php
    Flight::route('/', function () {
       Flight::view()->render('home.latte', ['title' => 'Mon Blog']);
    });
    
    Flight::route('/post/@slug', function ($slug) {
       Flight::view()->render('post.latte', ['title' => 'Article : ' . $slug, 'slug' => $slug]);
    });
    
    Flight::route('GET /create', function () {
       Flight::view()->render('create.latte', ['title' => 'Créer un Article']);
    });
  2. Mettre à jour index.php : Incluez le fichier des routes :

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

Étape 5 : Stocker et récupérer des articles de blog

Ajoutez les méthodes pour charger et sauvegarder des articles.

  1. Ajouter une méthode Posts : Dans index.php, ajoutez une méthode pour charger des articles :

    Flight::map('posts', function () {
       $file = __DIR__ . '/../data/posts.json';
       return json_decode(file_get_contents($file), true);
    });
  2. Mettre à jour les routes : Modifiez app/config/routes.php pour utiliser les articles :

    <?php
    Flight::route('/', function () {
       $posts = Flight::posts();
       Flight::view()->render('home.latte', [
           'title' => 'Mon 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' => 'Créer un Article']);
    });

Étape 6 : Créer des templates

Mettez à jour vos templates pour afficher des articles.

  1. Page de l'article (app/views/post.latte) :

    {extends 'layout.latte'}
    
    {block content}
        <h2>{$post['title']}</h2>
        <div class="post-content">
            <p>{$post['content']}</p>
        </div>
    {/block}

Étape 7 : Ajouter la création d'articles

Gérez la soumission du formulaire pour ajouter de nouveaux articles.

  1. Formulaire de création (app/views/create.latte) :

    {extends 'layout.latte'}
    
    {block content}
        <h2>{$title}</h2>
        <form method="POST" action="/create">
            <div class="form-group">
                <label for="title">Titre :</label>
                <input type="text" name="title" id="title" required>
            </div>
            <div class="form-group">
                <label for="content">Contenu :</label>
                <textarea name="content" id="content" required></textarea>
            </div>
            <button type="submit">Sauvegarder l'Article</button>
        </form>
    {/block}
  2. Ajouter une route POST : Dans 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. Testez-le :

    • Visitez http://localhost:8000/create.
    • Soumettez un nouvel article (par exemple, « Deuxième Article » avec un peu de contenu).
    • Vérifiez la page d'accueil pour voir qu'il est répertorié.

Étape 8 : Améliorer avec la gestion des erreurs

Surchargez la méthode notFound pour une meilleure expérience 404.

Dans index.php :

Flight::map('notFound', function () {
    Flight::view()->render('404.latte', ['title' => 'Page Non Trouvée']);
});

Créez app/views/404.latte :

{extends 'layout.latte'}

{block content}
    <h2>404 - {$title}</h2>
    <p>Désolé, cette page n'existe pas !</p>
{/block}

Prochaines étapes

Conclusion

Vous avez construit un blog simple avec Flight PHP ! Ce guide démontre des fonctionnalités clés comme le routage, le rendu de templates avec Latte et la gestion des soumissions de formulaires, le tout en gardant les choses légères. Explorez la documentation de Flight pour des fonctionnalités plus avancées afin de faire progresser votre blog !

License

La Licence MIT (MIT)
=====================

Droits d'auteur © `2024` `@mikecao, @n0nag0n`

La permission est accordée, gratuitement, à toute personne
obtenant une copie de ce logiciel et de la documentation associée
(fichiers du “Logiciel”), d'utiliser le Logiciel sans aucune
restriction, y compris, sans s'y limiter, les droits d'utiliser,
de copier, de modifier, de fusionner, de publier, de distribuer,
de concéder sous licence et/ou de vendre des copies du Logiciel,
et de permettre aux personnes auxquelles le Logiciel est fourni
de le faire, sous réserve des conditions suivantes :

L'avis de droit d'auteur ci-dessus et le présent avis de
permission doivent être inclus dans toutes les copies ou les
parties substantielles du Logiciel.

LE LOGICIEL EST FOURNI “TEL QUEL”, SANS GARANTIE D'AUCUNE
SORTES, EXPRESSE OU IMPLICITE, Y COMPRIS, MAIS SANS S'Y LIMITER,
LES GARANTIES DE QUALITÉ MARCHANDE, D'ADÉQUATION À UN USAGE
PARTICULIER ET D'ABSENCE DE CONTREFAÇON. EN AUCUN CAS, LES
AUTEURS OU LES TITULAIRES DES DROITS D'AUTEUR NE SAURAIENT ÊTRE
TENUS RESPONSABLES DE TOUTE RÉCLAMATION, DOMMAGE OU AUTRE
RESPONSABILITÉ, QUE CE SOIT DANS UNE ACTION DE CONTRAT, DE TORT
OU AUTRE, DÉCOULANT DE, HORS DE OU EN RELATION AVEC LE LOGICIEL
OU L'UTILISATION OU D'AUTRES TRANSACTIONS DANS LE LOGICIEL.

About

Cadre PHP Flight

Flight est un framework rapide, simple et extensible pour PHP, conçu pour les développeurs qui veulent accomplir les choses rapidement, sans complications. Que vous construisiez une application web classique, une API ultra-rapide, ou que vous expérimentiez avec les derniers outils alimentés par l'IA, la petite empreinte et la conception directe de Flight en font un choix parfait. Flight est destiné à être léger, mais il peut aussi gérer les exigences d'une architecture d'entreprise.

Pourquoi choisir Flight ?

Aperçu vidéo

Assez simple, n'est-ce pas ?
En savoir plus sur Flight dans la documentation !

Démarrage rapide

Pour une installation rapide et basique, installez-le avec Composer :

composer require flightphp/core

Ou vous pouvez télécharger un zip du dépôt ici. Ensuite, vous aurez un fichier index.php de base comme suit :

<?php

// si installé avec composer
require 'vendor/autoload.php';
// ou si installé manuellement par fichier zip
// require 'flight/Flight.php';

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

Flight::route('/json', function() {
  Flight::json([
    'hello' => 'world'
  ]);
});

Flight::start();

C'est tout ! Vous avez une application Flight de base. Vous pouvez maintenant exécuter ce fichier avec php -S localhost:8000 et visiter http://localhost:8000 dans votre navigateur pour voir le résultat.

Application squelette/modèle

Il y a un exemple d'application pour vous aider à démarrer votre projet avec Flight. Elle dispose d'une mise en page structurée, de configurations de base toutes prêtes et gère les scripts Composer dès le départ ! Jetez un œil à flightphp/skeleton pour un projet prêt à l'emploi, ou visitez la page exemples pour trouver de l'inspiration. Vous voulez voir comment l'IA s'intègre ? Explorez des exemples alimentés par l'IA.

Installation de l'application squelette

C'est facile !

# Créez le nouveau projet
composer create-project flightphp/skeleton my-project/
# Entrez dans le répertoire de votre nouveau projet
cd my-project/
# Lancez le serveur de développement local pour commencer immédiatement !
composer start

Cela créera la structure du projet, configurera les fichiers dont vous avez besoin, et vous serez prêt à partir !

Performances élevées

Flight est l'un des frameworks PHP les plus rapides du marché. Son cœur léger signifie moins de surcharge et plus de vitesse, parfait pour les applications traditionnelles et les projets modernes alimentés par l'IA. Vous pouvez voir tous les benchmarks sur TechEmpower.

Voici le benchmark ci-dessous avec d'autres frameworks PHP populaires.

Framework Reqs/sec en texte brut Reqs/sec en JSON
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 et l'IA

Curieux de savoir comment il gère l'IA ? Découvrez comment Flight facilite le travail avec votre LLM de codage préféré !

Communauté

Nous sommes sur Matrix Chat

Matrix

Et Discord

Contribution

Il y a deux façons de contribuer à Flight :

  1. Contribuez au framework principal en visitant le dépôt principal.
  2. Aidez à améliorer les docs ! Ce site de documentation est hébergé sur Github. Si vous repérez une erreur ou souhaitez améliorer quelque chose, n'hésitez pas à soumettre une pull request. Nous adorons les mises à jour et les nouvelles idées, surtout autour de l'IA et des nouvelles technologies !

Exigences

Flight nécessite PHP 7.4 ou une version supérieure.

Note : PHP 7.4 est pris en charge car, au moment de l'écriture (2024), PHP 7.4 est la version par défaut pour certaines distributions Linux LTS. Forcer un passage à PHP >8 causerait beaucoup de problèmes pour ces utilisateurs. Le framework prend aussi en charge PHP >8.

Licence

Flight est publié sous la licence MIT.

Awesome-plugins/php_cookie

Cookies

overclokk/cookie est une bibliothèque simple pour gérer les cookies au sein de votre application.

Installation

L'installation est simple avec composer.

composer require overclokk/cookie

Utilisation

L'utilisation est aussi simple que d'enregistrer une nouvelle méthode sur la classe Flight.


use Overclokk\Cookie\Cookie;

/*
 * Définissez dans votre fichier bootstrap ou public/index.php
 */

Flight::register('cookie', Cookie::class);

/**
 * ExampleController.php
 */

class ExampleController {
    public function login() {
        // Définir un cookie

        // vous voudrez que ce soit faux pour obtenir une nouvelle instance
        // utilisez le commentaire ci-dessous si vous souhaitez l'autocomplétion
        /** @var \Overclokk\Cookie\Cookie $cookie */
        $cookie = Flight::cookie(false);
        $cookie->set(
            'stay_logged_in', // nom du cookie
            '1', // la valeur que vous souhaitez définir
            86400, // nombre de secondes pendant lesquelles le cookie doit durer
            '/', // chemin où le cookie sera disponible
            'example.com', // domaine où le cookie sera disponible
            true, // le cookie ne sera transmis que sur une connexion sécurisée HTTPS
            true // le cookie ne sera disponible que via le protocole HTTP
        );

        // éventuellement, si vous voulez conserver les valeurs par défaut
        // et avoir un moyen rapide de définir un cookie pour une longue durée
        $cookie->forever('stay_logged_in', '1');
    }

    public function home() {
        // Vérifiez si vous avez le cookie
        if (Flight::cookie()->has('stay_logged_in')) {
            // placez-les dans la zone du tableau de bord par exemple.
            Flight::redirect('/dashboard');
        }
    }
}

Awesome-plugins/php_encryption

Chiffrement PHP

defuse/php-encryption est une bibliothèque qui peut être utilisée pour chiffrer et déchiffrer des données. Il est assez simple de commencer à chiffrer et déchiffrer des données. Ils ont un didacticiel qui aide à expliquer les bases de l'utilisation de la bibliothèque ainsi que les implications importantes en matière de sécurité concernant le chiffrement.

Installation

L'installation est simple avec Composer.

composer require defuse/php-encryption

Configuration

Ensuite, vous devrez générer une clé de chiffrement.

vendor/bin/generate-defuse-key

Cela affichera une clé que vous devrez conserver en sécurité. Vous pourriez conserver la clé dans votre fichier app/config/config.php dans le tableau en bas du fichier. Ce n'est pas l'endroit parfait, mais c'est au moins quelque chose.

Utilisation

Maintenant que vous avez la bibliothèque et une clé de chiffrement, vous pouvez commencer à chiffrer et déchiffrer des données.


use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;

/*
 * Défini dans votre fichier bootstrap ou public/index.php
 */

// Méthode de chiffrement
Flight::map('encrypt', function($raw_data) {
    $encryption_key = /* $config['encryption_key'] or a file_get_contents of where you put the key */;
    return Crypto::encrypt($raw_data, Key::loadFromAsciiSafeString($encryption_key));
});

// Méthode de déchiffrement
Flight::map('decrypt', function($encrypted_data) {
    $encryption_key = /* $config['encryption_key'] or a file_get_contents of where you put the key */;
    try {
        $raw_data = Crypto::decrypt($encrypted_data, Key::loadFromAsciiSafeString($encryption_key));
    } catch (Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException $ex) {
        // Une attaque ! Soit la mauvaise clé a été chargée, soit le texte chiffré a
        // changé depuis sa création -- corrompu dans la base de données ou
        // intentionnellement modifié par Eve essayant de mener une attaque.

        // ... gérer ce cas de manière adaptée à votre application ...
    }
    return $raw_data;
});

Flight::route('/encrypt', function() {
    $encrypted_data = Flight::encrypt('Ceci est un secret');
    echo $encrypted_data;
});

Flight::route('/decrypt', function() {
    $encrypted_data = '...'; // Obtenir les données chiffrées de quelque part
    $decrypted_data = Flight::decrypt($encrypted_data);
    echo $decrypted_data;
});

Awesome-plugins/php_file_cache

flightphp/cache

Classe de mise en cache PHP légère, simple et autonome en fichier, dérivée de Wruczek/PHP-File-Cache

Avantages

Ce site de documentation utilise cette bibliothèque pour mettre en cache chaque page !

Cliquez ici pour voir le code.

Installation

Installez via composer :

composer require flightphp/cache

Utilisation

L'utilisation est assez simple. Cela enregistre un fichier de cache dans le répertoire de cache.

use flight\Cache;

$app = Flight::app();

// Vous passez le répertoire où le cache sera stocké dans le constructeur
$app->register('cache', Cache::class, [ __DIR__ . '/../cache/' ], function(Cache $cache) {

    // Cela garantit que le cache n'est utilisé que en mode production
    // ENVIRONMENT est une constante définie dans votre fichier bootstrap ou ailleurs dans votre application
    $cache->setDevMode(ENVIRONMENT === 'development');
});

Obtenir une valeur de cache

Vous utilisez la méthode get() pour obtenir une valeur mise en cache. Si vous voulez une méthode pratique qui rafraîchira le cache s'il est expiré, vous pouvez utiliser refreshIfExpired().


// Obtenir l'instance de cache
$cache = Flight::cache();
$data = $cache->refreshIfExpired('simple-cache-test', function () {
    return date("H:i:s"); // retourner les données à mettre en cache
}, 10); // 10 secondes

// ou
$data = $cache->get('simple-cache-test');
if(empty($data)) {
    $data = date("H:i:s");
    $cache->set('simple-cache-test', $data, 10); // 10 secondes
}

Stocker une valeur de cache

Vous utilisez la méthode set() pour stocker une valeur dans le cache.

Flight::cache()->set('simple-cache-test', 'my cached data', 10); // 10 secondes

Effacer une valeur de cache

Vous utilisez la méthode delete() pour effacer une valeur dans le cache.

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

Vérifier si une valeur de cache existe

Vous utilisez la méthode exists() pour vérifier si une valeur existe dans le cache.

if(Flight::cache()->exists('simple-cache-test')) {
    // faire quelque chose
}

Vider le cache

Vous utilisez la méthode flush() pour vider l'ensemble du cache.

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

Extraire les métadonnées avec le cache

Si vous voulez extraire les horodatages et autres métadonnées sur une entrée de cache, assurez-vous de passer true comme paramètre correct.

$data = $cache->refreshIfExpired("simple-cache-meta-test", function () {
    echo "Refreshing data!" . PHP_EOL;
    return date("H:i:s"); // retourner les données à mettre en cache
}, 10, true); // true = retourner avec métadonnées
// ou
$data = $cache->get("simple-cache-meta-test", true); // true = retourner avec métadonnées

/*
Exemple d'élément mis en cache récupéré avec métadonnées :
{
    "time":1511667506, <-- horodatage unix de sauvegarde
    "expire":10,       <-- temps d'expiration en secondes
    "data":"04:38:26", <-- données désérialisées
    "permanent":false
}

En utilisant les métadonnées, nous pouvons, par exemple, calculer quand l'élément a été sauvegardé ou quand il expire
Nous pouvons aussi accéder aux données elles-mêmes avec la clé "data"
*/

$expiresin = ($data["time"] + $data["expire"]) - time(); // obtenir l'horodatage unix quand les données expirent et soustraire l'horodatage actuel
$cacheddate = $data["data"]; // nous accédons aux données elles-mêmes avec la clé "data"

echo "Dernière sauvegarde de cache : $cacheddate, expire dans $expiresin secondes";

Documentation

Visitez https://github.com/flightphp/cache pour voir le code. Assurez-vous de consulter le dossier examples pour des façons supplémentaires d'utiliser le cache.

Awesome-plugins/permissions

FlightPHP/Autorisations

Il s'agit d'un module d'autorisations qui peut être utilisé dans vos projets si vous avez plusieurs rôles dans votre application et que chaque rôle a une fonctionnalité légèrement différente. Ce module vous permet de définir des autorisations pour chaque rôle, puis de vérifier si l'utilisateur actuel a l'autorisation d'accéder à une certaine page ou d'effectuer une certaine action.

Cliquez ici pour accéder au dépôt sur GitHub.

Installation

Exécutez composer require flightphp/permissions et c'est parti!

Utilisation

Tout d'abord, vous devez configurer vos autorisations, puis vous indiquez à votre application ce que signifient les autorisations. En fin de compte, vous vérifierez vos autorisations avec $Permissions->has(), ->can(), ou is(). has() et can() ont la même fonctionnalité, mais sont nommées différemment pour rendre votre code plus lisible.

Exemple de base

Supposons que vous ayez une fonctionnalité dans votre application qui vérifie si un utilisateur est connecté. Vous pouvez créer un objet d'autorisations comme ceci:

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

// some code 

// then you probably have something that tells you who the current role is of the person
// likely you have something where you pull the current role
// from a session variable which defines this
// after someone logs in, otherwise they will have a 'guest' or 'public' role.
$current_role = 'admin';

// setup permissions
$permission = new \flight\Permission($current_role);
$permission->defineRule('loggedIn', function($current_role) {
    return $current_role !== 'guest';
});

// You'll probably want to persist this object in Flight somewhere
Flight::set('permission', $permission);

Ensuite, dans un contrôleur quelque part, vous pourriez avoir quelque chose comme ceci.

<?php

// some controller
class SomeController {
    public function someAction() {
        $permission = Flight::get('permission');
        if ($permission->has('loggedIn')) {
            // do something
        } else {
            // do something else
        }
    }
}

Vous pouvez également utiliser ceci pour suivre s'ils ont l'autorisation de faire quelque chose dans votre application. Par exemple, si vous avez un moyen pour les utilisateurs d'interagir avec des publications sur votre logiciel, vous pouvez vérifier s'ils ont l'autorisation d'effectuer certaines actions.

$current_role = 'admin';

// setup permissions
$permission = new \flight\Permission($current_role);
$permission->defineRule('post', function($current_role) {
    if($current_role === 'admin') {
        $permissions = ['create', 'read', 'update', 'delete'];
    } else if($current_role === 'editor') {
        $permissions = ['create', 'read', 'update'];
    } else if($current_role === 'author') {
        $permissions = ['create', 'read'];
    } else if($current_role === 'contributor') {
        $permissions = ['create'];
    } else {
        $permissions = [];
    }
    return $permissions;
});
Flight::set('permission', $permission);

Ensuite, dans un contrôleur quelque part...

class PostController {
    public function create() {
        $permission = Flight::get('permission');
        if ($permission->can('post.create')) {
            // do something
        } else {
            // do something else
        }
    }
}

Injection de dépendances

Vous pouvez injecter des dépendances dans la fonction de fermeture qui définit les autorisations. C'est utile si vous avez un type de bascule, d'identifiant ou tout autre point de données que vous voulez vérifier. Le même principe s'applique aux appels de type Classe->Méthode, sauf que vous définissez les arguments dans la méthode.

Fonctions de fermeture

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

// dans votre fichier de contrôleur
public function createOrder() {
    $MyDependency = Flight::myDependency();
    $permission = Flight::get('permission');
    if ($permission->can('order.create', $MyDependency)) {
        // do something
    } else {
        // do something else
    }
}

Classes

namespace MyApp;

class Permissions {

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

Raccourci pour définir des autorisations avec des classes

Vous pouvez également utiliser des classes pour définir vos autorisations. C'est utile si vous avez beaucoup d'autorisations et que vous voulez garder votre code propre. Vous pouvez faire quelque chose comme ceci:

<?php

// code de démarrage
$Permissions = new \flight\Permission($current_role);
$Permissions->defineRule('order', 'MyApp\Permissions->order');

// myapp/Permissions.php
namespace MyApp;

class Permissions {

    public function order(string $current_role, int $user_id) {
        // En supposant que vous avez configuré cela au préalable
        /** @var \flight\database\PdoWrapper $db */
        $db = Flight::db();
        $allowed_permissions = [ 'read' ]; // tout le monde peut consulter une commande
        if($current_role === 'manager') {
            $allowed_permissions[] = 'create'; // les gestionnaires peuvent créer des commandes
        }
        $some_special_toggle_from_db = $db->fetchField('SELECT some_special_toggle FROM settings WHERE id = ?', [ $user_id ]);
        if($some_special_toggle_from_db) {
            $allowed_permissions[] = 'update'; // si l'utilisateur a une bascule spéciale, il peut mettre à jour des commandes
        }
        if($current_role === 'admin') {
            $allowed_permissions[] = 'delete'; // les administrateurs peuvent supprimer des commandes
        }
        return $allowed_permissions;
    }
}

L'astuce est que vous pouvez également utiliser un raccourci (qui peut également être mis en cache!!!) où vous dites simplement à la classe d'autorisations de mapper toutes les méthodes d'une classe en autorisations. Ainsi, si vous avez une méthode nommée order() et une méthode nommée company(), cela sera automatiquement cartographié pour que vous puissiez simplement exécuter $Permissions->has('order.read') ou $Permissions->has('company.read') et cela fonctionnera. Définir cela est très difficile, donc suivez moi ici. Vous avez juste besoin de faire ceci:

Créez la classe des autorisations que vous souhaitez regrouper.

class MyPermissions {
    public function order(string $current_role, int $order_id = 0): array {
        // code pour déterminer les autorisations
        return $permissions_array;
    }

    public function company(string $current_role, int $company_id): array {
        // code pour déterminer les autorisations
        return $permissions_array;
    }
}

Puis rendez les autorisations découvrables en utilisant cette bibliothèque.

$Permissions = new \flight\Permission($current_role);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class);
Flight::set('permissions', $Permissions);

Enfin, appelez l'autorisation dans votre code pour vérifier si l'utilisateur est autorisé à effectuer une autorisation donnée.

class SomeController {
    public function createOrder() {
        if(Flight::get('permissions')->can('order.create') === false) {
            die('Vous ne pouvez pas créer une commande. Désolé!');
        }
    }
}

Mise en cache

Pour activer la mise en cache, consultez la bibliothèque simple wruczak/phpfilecache. Un exemple pour l'activer est ci-dessous.


// cet $app peut faire partie de votre code, ou
// vous pouvez simplement passer null et il
// récupérera de Flight::app() dans le constructeur
$app = Flight::app();

// Pour l'instant, cela accepte cela comme cache de fichier. D'autres peuvent facilement
// être ajoutés à l'avenir. 
$Cache = new Wruczek\PhpFileCache\PhpFileCache;

$Permissions = new \flight\Permission($current_role, $app, $Cache);
$Permissions->defineRulesFromClassMethods(MyApp\Permissions::class, 3600); // 3600 est le nombre de secondes pendant lesquels conserver cette mise en cache. Laissez ceci vide pour ne pas utiliser la mise en cache

Awesome-plugins/simple_job_queue

File d'attente de travail simple

La file d'attente de travail simple est une bibliothèque qui peut être utilisée pour traiter des travaux de manière asynchrone. Elle peut être utilisée avec beanstalkd, MySQL/MariaDB, SQLite et PostgreSQL.

Installation

composer require n0nag0n/simple-job-queue

Utilisation

Pour que cela fonctionne, vous devez avoir un moyen d'ajouter des travaux à la file d'attente et un moyen de traiter les travaux (un travailleur). Ci-dessous, des exemples de la façon d'ajouter un travail à la file d'attente et de traiter le travail.

Ajout à Flight

Ajouter cela à Flight est simple et se fait en utilisant la méthode register(). Ci-dessous un exemple de la façon d'ajouter cela à Flight.

<?php
require 'vendor/autoload.php';

// Changez ['mysql'] en ['beanstalkd'] si vous voulez utiliser beanstalkd
Flight::register('queue', n0nag0n\Job_Queue::class, ['mysql'], function($Job_Queue) {
    // si vous avez déjà une connexion PDO sur Flight::db();
    $Job_Queue->addQueueConnection(Flight::db());

    // ou si vous utilisez beanstalkd/Pheanstalk
    $pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
    $Job_Queue->addQueueConnection($pheanstalk);
});

Ajout d'un nouveau travail

Lorsque vous ajoutez un travail, vous devez spécifier un pipeline (file d'attente). Cela est comparable à un canal dans RabbitMQ ou un tube dans beanstalkd.

<?php
Flight::queue()->selectPipeline('send_important_emails');
Flight::queue()->addJob(json_encode([ 'something' => 'that', 'ends' => 'up', 'a' => 'string' ]));

Exécution d'un travailleur

Voici un exemple de fichier sur comment exécuter un travailleur.

<?php

require 'vendor/autoload.php';

$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Connexion PDO
$PDO = new PDO('mysql:dbname=testdb;host=127.0.0.1', 'user', 'pass');
$Job_Queue->addQueueConnection($PDO);

// ou si vous utilisez beanstalkd/Pheanstalk
$pheanstalk = Pheanstalk\Pheanstalk::create('127.0.0.1');
$Job_Queue->addQueueConnection($pheanstalk);

$Job_Queue->watchPipeline('send_important_emails');
while(true) {
    $job = $Job_Queue->getNextJobAndReserve();

    // ajustez selon ce qui vous fait dormir mieux la nuit (pour les files d'attente de base de données uniquement, beanstalkd n'a pas besoin de cette instruction conditionnelle)
    if(empty($job)) {
        usleep(500000);
        continue;
    }

    echo "Traitement de {$job['id']}\n";
    $payload = json_decode($job['payload'], true);

    try {
        $result = doSomethingThatDoesSomething($payload);

        if($result === true) {
            $Job_Queue->deleteJob($job);
        } else {
            // cela le retire de la file d'attente prête et le met dans une autre file d'attente qui peut être récupérée et "kickée" plus tard.
            $Job_Queue->buryJob($job);
        }
    } catch(Exception $e) {
        $Job_Queue->buryJob($job);
    }
}

Gestion des processus longs avec Supervisord

Supervisord est un système de contrôle de processus qui garantit que vos processus travailleurs restent en cours d'exécution en continu. Voici un guide plus complet sur la façon de le configurer avec votre travailleur Simple Job Queue :

Installation de Supervisord

# Sur Ubuntu/Debian
sudo apt-get install supervisor

# Sur CentOS/RHEL
sudo yum install supervisor

# Sur macOS avec Homebrew
brew install supervisor

Création d'un script de travailleur

Tout d'abord, enregistrez votre code de travailleur dans un fichier PHP dédié :

<?php

require 'vendor/autoload.php';

$Job_Queue = new n0nag0n\Job_Queue('mysql');
// Connexion PDO
$PDO = new PDO('mysql:dbname=your_database;host=127.0.0.1', 'username', 'password');
$Job_Queue->addQueueConnection($PDO);

// Définir le pipeline à surveiller
$Job_Queue->watchPipeline('send_important_emails');

// Journaliser le début du travailleur
echo date('Y-m-d H:i:s') . " - Travailleur démarré\n";

while(true) {
    $job = $Job_Queue->getNextJobAndReserve();

    if(empty($job)) {
        usleep(500000); // Dormir pendant 0,5 secondes
        continue;
    }

    echo date('Y-m-d H:i:s') . " - Traitement du travail {$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') . " - Travail {$job['id']} terminé avec succès\n";
        } else {
            $Job_Queue->buryJob($job);
            echo date('Y-m-d H:i:s') . " - Travail {$job['id']} échoué, enterré\n";
        }
    } catch(Exception $e) {
        $Job_Queue->buryJob($job);
        echo date('Y-m-d H:i:s') . " - Exception lors du traitement du travail {$job['id']}: {$e->getMessage()}\n";
    }
}

Configuration de Supervisord

Créez un fichier de configuration pour votre travailleur :

[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

Options de configuration clés :

Gestion des travailleurs avec Supervisorctl

Après avoir créé ou modifié la configuration :

# Recharger la configuration du superviseur
sudo supervisorctl reread
sudo supervisorctl update

# Contrôler des processus de travailleur spécifiques
sudo supervisorctl start email_worker:*
sudo supervisorctl stop email_worker:*
sudo supervisorctl restart email_worker:*
sudo supervisorctl status email_worker:*

Exécution de plusieurs pipelines

Pour plusieurs pipelines, créez des fichiers de travailleurs et des configurations distincts :

[program:email_worker]
command=php /path/to/email_worker.php
# ... autres configurations ...

[program:notification_worker]
command=php /path/to/notification_worker.php
# ... autres configurations ...

Surveillance et journaux

Vérifiez les journaux pour surveiller l'activité du travailleur :

# Voir les journaux
sudo tail -f /var/log/simple_job_queue.log

# Vérifier le statut
sudo supervisorctl status

Cette configuration garantit que vos travailleurs de tâches continuent d'exécuter même après des plantages, des redémarrages de serveur ou d'autres problèmes, rendant votre système de file d'attente fiable pour les environnements de production.

Awesome-plugins/n0nag0n_wordpress

Intégration WordPress : n0nag0n/wordpress-integration-for-flight-framework

Voulez-vous utiliser Flight PHP dans votre site WordPress ? Ce plugin rend cela très simple ! Avec n0nag0n/wordpress-integration-for-flight-framework, vous pouvez exécuter une application Flight complète directement aux côtés de votre installation WordPress—parfait pour créer des API personnalisées, des microservices ou même des applications complètes sans quitter l'environnement WordPress.


Que fait-il ?

Installation

  1. Téléchargez le dossier flight-integration dans votre répertoire /wp-content/plugins/.
  2. Activez le plugin dans l'administration WordPress (menu Plugins).
  3. Allez dans Paramètres > Flight Framework pour configurer le plugin.
  4. Définissez le chemin du vendeur vers votre installation Flight (ou utilisez Composer pour installer Flight).
  5. Configurez le chemin de votre dossier d'application et créez la structure de dossiers (le plugin peut vous aider à cela !).
  6. Commencez à construire votre application Flight !

Exemples d'utilisation

Exemple de route basique

Dans votre fichier app/config/routes.php :

Flight::route('GET /api/hello', function() {
    Flight::json(['message' => 'Hello World!']);
});

Exemple de contrôleur

Créez un contrôleur dans app/controllers/ApiController.php :

namespace app\controllers;

use Flight;

class ApiController {
    public function getUsers() {
        // Vous pouvez utiliser les fonctions WordPress à l'intérieur de Flight !
        $users = get_users();
        $result = [];
        foreach($users as $user) {
            $result[] = [
                'id' => $user->ID,
                'name' => $user->display_name,
                'email' => $user->user_email
            ];
        }
        Flight::json($result);
    }
}

Puis, dans votre routes.php :

Flight::route('GET /api/users', [app\controllers\ApiController::class, 'getUsers']);

FAQ

Q: Dois-je connaître Flight pour utiliser ce plugin ?
A: Oui, ceci s'adresse aux développeurs qui souhaitent utiliser Flight au sein de WordPress. Une connaissance de base du routage et de la gestion des requêtes de Flight est recommandée.

Q: Cela va-t-il ralentir mon site WordPress ?
A: Non ! Le plugin ne traite que les requêtes qui correspondent à vos routes Flight. Toutes les autres requêtes vont à WordPress comme d'habitude.

Q: Puis-je utiliser les fonctions WordPress dans mon application Flight ?
A: Absolument ! Vous avez un accès complet à toutes les fonctions, hooks et variables globales de WordPress depuis vos routes et contrôleurs Flight.

Q: Comment créer des routes personnalisées ?
A: Définissez vos routes dans le fichier config/routes.php de votre dossier d'application. Consultez le fichier exemple créé par le générateur de structure de dossiers pour des exemples.

Journal des modifications

1.0.0
Version initiale.


Pour plus d'informations, consultez le GitHub repo.

Awesome-plugins/ghost_session

Ghostff/Session

Gestionnaire de sessions PHP (non-bloquant, flash, segment, chiffrement de session). Utilise PHP open_ssl pour le chiffrement/déchiffrement optionnel des données de session. Prend en charge File, MySQL, Redis et Memcached.

Cliquez ici pour voir le code.

Installation

Installez avec composer.

composer require ghostff/session

Configuration de base

Vous n'êtes pas obligé de passer quoi que ce soit pour utiliser les paramètres par défaut avec votre session. Vous pouvez en lire plus sur les paramètres dans le Github Readme.

use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

// une chose à retenir est que vous devez valider votre session à chaque chargement de page
// ou vous devrez exécuter auto_commit dans votre configuration.

Exemple simple

Voici un exemple simple de la façon dont vous pourriez utiliser cela.

Flight::route('POST /login', function() {
    $session = Flight::session();

    // effectuez votre logique de connexion ici
    // valider le mot de passe, etc.

    // si la connexion est réussie
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // à tout moment que vous écrivez dans la session, vous devez la valider délibérément.
    $session->commit();
});

// Cette vérification pourrait se faire dans la logique de la page restreinte, ou être enveloppée dans un middleware.
Flight::route('/some-restricted-page', function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }

    // effectuez votre logique de page restreinte ici
});

// la version middleware
Flight::route('/some-restricted-page', function() {
    // logique de page régulière
})->addMiddleware(function() {
    $session = Flight::session();

    if(!$session->get('is_logged_in')) {
        Flight::redirect('/login');
    }
});

Exemple plus complexe

Voici un exemple plus complexe de la façon dont vous pourriez utiliser cela.

use Ghostff\Session\Session;

require 'vendor/autoload.php';

$app = Flight::app();

// définissez un chemin personnalisé vers votre fichier de configuration de session en tant que premier argument
// ou donnez-lui le tableau personnalisé
$app->register('session', Session::class, [ 
    [
        // si vous voulez stocker vos données de session dans une base de données (utile pour quelque chose comme, "me déconnecter de tous les appareils" fonctionnalité)
        Session::CONFIG_DRIVER        => Ghostff\Session\Drivers\MySql::class,
        Session::CONFIG_ENCRYPT_DATA  => true,
        Session::CONFIG_SALT_KEY      => hash('sha256', 'my-super-S3CR3T-salt'), // veuillez changer cela pour quelque chose d'autre
        Session::CONFIG_AUTO_COMMIT   => true, // ne le faites que si c'est nécessaire et/ou si c'est difficile de faire commit() sur votre session.
                                                // de plus, vous pourriez faire Flight::after('start', function() { Flight::session()->commit(); });
        Session::CONFIG_MYSQL_DS         => [
            'driver'    => 'mysql',             # Pilote de base de données pour PDO dns ex.(mysql:host=...;dbname=...)
            'host'      => '127.0.0.1',         # Hôte de la base de données
            'db_name'   => 'my_app_database',   # Nom de la base de données
            'db_table'  => 'sessions',          # Table de la base de données
            'db_user'   => 'root',              # Nom d'utilisateur de la base de données
            'db_pass'   => '',                  # Mot de passe de la base de données
            'persistent_conn'=> false,          # Éviter le surcoût d'établir une nouvelle connexion à chaque fois qu'un script doit communiquer avec une base de données, ce qui accélère l'application web. TROUVEZ LE DESSUS VOUS-MÊME
        ]
    ] 
]);

Aide ! Mes données de session ne persistent pas !

Vous configurez vos données de session et elles ne persistent pas entre les requêtes ? Vous avez peut-être oublié de valider vos données de session. Vous pouvez le faire en appelant $session->commit() après avoir défini vos données de session.

Flight::route('POST /login', function() {
    $session = Flight::session();

    // effectuez votre logique de connexion ici
    // valider le mot de passe, etc.

    // si la connexion est réussie
    $session->set('is_logged_in', true);
    $session->set('user', $user);

    // à tout moment que vous écrivez dans la session, vous devez la valider délibérément.
    $session->commit();
});

L'autre moyen d'y remédier est, lorsque vous configurez votre service de session, de définir auto_commit sur true dans votre configuration. Cela validera automatiquement vos données de session après chaque requête.

$app->register('session', Session::class, [ 'path/to/session_config.php', bin2hex(random_bytes(32)) ], function(Session $session) {
        $session->updateConfiguration([
            Session::CONFIG_AUTO_COMMIT   => true,
        ]);
    }
);

De plus, vous pourriez faire Flight::after('start', function() { Flight::session()->commit(); }); pour valider vos données de session après chaque requête.

Documentation

Visitez le Github Readme pour la documentation complète. Les options de configuration sont bien documentées dans le fichier default_config.php lui-même. Le code est simple à comprendre si vous vouliez parcourir ce package vous-même.

Awesome-plugins/async

Async

Async est un petit package pour le framework Flight qui vous permet d'exécuter vos applications Flight dans des serveurs et des runtimes asynchrones comme Swoole, AdapterMan, ReactPHP, Amp, RoadRunner, Workerman, etc. Par défaut, il inclut des adaptateurs pour Swoole et AdapterMan.

L'objectif : développer et déboguer avec PHP-FPM (ou le serveur intégré) et passer à Swoole (ou un autre pilote asynchrone) pour la production avec des changements minimaux.

Exigences

Installation

Installez via Composer :

composer require flightphp/async

Si vous prévoyez d'exécuter avec Swoole, installez l'extension :

# en utilisant pecl
pecl install swoole
# ou openswoole
pecl install openswoole

# ou avec un gestionnaire de paquets (exemple Debian/Ubuntu)
sudo apt-get install php-swoole

Exemple rapide avec Swoole

Voici ci-dessous une configuration minimale qui montre comment supporter à la fois PHP-FPM (ou le serveur intégré) et Swoole en utilisant le même code source.

Fichiers dont vous aurez besoin dans votre projet :

index.php

Ce fichier est un simple interrupteur qui force l'application à s'exécuter en mode PHP pour le développement.

// index.php
<?php

define('NOT_SWOOLE', true);

include 'swoole_server.php';

swoole_server.php

Ce fichier initialise votre application Flight et démarrera le pilote Swoole lorsque NOT_SWOOLE n'est pas défini.

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

Un pilote concis montrant comment relier les requêtes Swoole à Flight en utilisant AsyncBridge et les adaptateurs Swoole.

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

Exécution du serveur

Astuce : Pour la production, utilisez un proxy inverse (Nginx) devant Swoole pour gérer TLS, les fichiers statiques et l'équilibrage de charge.

Notes de configuration

Le pilote Swoole expose plusieurs options de configuration :

Ajustez ces paramètres en fonction des ressources de votre hôte et des schémas de trafic.

Gestion des erreurs

AsyncBridge traduit les erreurs Flight en réponses HTTP appropriées. Vous pouvez également ajouter une gestion d'erreurs au niveau des routes :

$app->route('/*', function() use ($app) {
    try {
        // route logic
    } catch (Exception $e) {
        $app->response()->status(500);
        $app->json(['error' => $e->getMessage()]);
    }
});

AdapterMan et autres runtimes

AdapterMan est supporté en tant qu'adaptateur de runtime alternatif. Le package est conçu pour être adaptable — ajouter ou utiliser d'autres adaptateurs suit généralement le même schéma : convertir la requête/réponse du serveur en requête/réponse Flight via AsyncBridge et les adaptateurs spécifiques au runtime.

Awesome-plugins/migrations

Migrations

Une migration pour votre projet suit toutes les modifications de la base de données impliquées dans votre projet. byjg/php-migration est une bibliothèque centrale très utile pour vous aider à démarrer.

Installing

PHP Library

Si vous souhaitez utiliser uniquement la bibliothèque PHP dans votre projet :

composer require "byjg/migration"

Command Line Interface

L'interface de ligne de commande est autonome et ne nécessite pas d'installation avec votre projet.

Vous pouvez l'installer globalement et créer un lien symbolique

composer require "byjg/migration-cli"

Veuillez visiter byjg/migration-cli pour obtenir plus d'informations sur Migration CLI.

Bases de données prises en charge

Base de données Pilote Chaîne de connexion
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

Comment ça fonctionne ?

La migration de base de données utilise le SQL PUR pour gérer la version de la base de données. Pour faire fonctionner, vous devez :

Les scripts SQL

Les scripts sont divisés en trois ensemble de scripts :

Le répertoire des scripts est :

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

Environnement de développement multi

Si vous travaillez avec plusieurs développeurs et plusieurs branches, il est trop difficile de déterminer quel est le prochain numéro.

Dans ce cas, vous avez le suffixe "-dev" après le numéro de version.

Voyez le scénario :

Dans les deux cas, les développeurs créeront un fichier appelé 43-dev.sql. Les deux développeurs migreront VERS LE HAUT et VERS LE BAS sans problème et votre version locale sera 43.

Mais le développeur 1 a fusionné vos modifications et créé une version finale 43.sql (git mv 43-dev.sql 43.sql). Si le développeur 2 met à jour sa branche locale, il aura un fichier 43.sql (du dev 1) et son fichier 43-dev.sql. S'il essaie de migrer VERS LE HAUT ou VERS LE BAS, le script de migration ne pourra pas se faire et l'alertera qu'il y a DEUX versions 43. Dans ce cas, le développeur 2 devra mettre à jour son fichier pour faire 44-dev.sql et continuer à travailler jusqu'à ce que les modifications soient fusionnées et génèrent une version finale.

Utiliser l'API PHP et l'intégrer dans vos projets

L'utilisation de base est

Voici un exemple :

<?php
// Créer l'URI de connexion
// Voir plus : https://github.com/byjg/anydataset#connection-based-on-uri
$connectionUri = new \ByJG\Util\Uri('mysql://migrateuser:migratepwd@localhost/migratedatabase');

// Enregistrer les bases de données ou les bases de données qui peuvent gérer cette URI :
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);

// Créer l'instance Migration
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');

// Ajouter une fonction de rappel de progression pour recevoir des informations sur l'exécution
$migration->addCallbackProgress(function ($action, $currentVersion, $fileInfo) {
    echo "$action, $currentVersion, ${fileInfo['description']}\n";
});

// Restaurer la base de données en utilisant le script "base.sql"
// et exécuter TOUS les scripts existants pour augmenter la version de la base de données à la dernière version
$migration->reset();

// Exécuter TOUS les scripts existants pour augmenter ou diminuer la version de la base de données
// depuis la version actuelle jusqu'au numéro de version ; 
// Si le numéro de version n'est pas spécifié, effectuez une migration jusqu'à la dernière version de base de données
$migration->update($version = null);

L'objet Migration contrôle la version de la base de données.

Création d'un contrôle de version dans votre projet

<?php
// Enregistrer les bases de données ou les bases de données qui peuvent gérer cette URI :
\ByJG\DbMigration\Migration::registerDatabase(\ByJG\DbMigration\Database\MySqlDatabase::class);

// Créer l'instance Migration
$migration = new \ByJG\DbMigration\Migration($connectionUri, '.');

// Cette commande va créer la table de version dans votre base de données
$migration->createVersion();

Obtenir la version actuelle

<?php
$migration->getCurrentVersion();

Ajouter un rappel pour contrôler la progression

<?php
$migration->addCallbackProgress(function ($command, $version, $fileInfo) {
    echo "Exécution de la commande : $command à la version $version - ${fileInfo['description']}, ${fileInfo['exists']}, ${fileInfo['file']}, ${fileInfo['checksum']}\n";
});

Obtenir l'instance du pilote de la base de données

<?php
$migration->getDbDriver();

Pour l'utiliser, veuillez visiter : https://github.com/byjg/anydataset-db

Éviter la migration partielle (non disponible pour MySQL)

Une migration partielle se produit lorsque le script de migration est interrompu au milieu du processus en raison d'une erreur ou d'une interruption manuelle.

La table de migration sera avec le statut partiel en cours ou partiel en bas et doit être corrigée manuellement avant de pouvoir migrer à nouveau.

Pour éviter cette situation, vous pouvez spécifier que la migration sera exécutée dans un contexte transactionnel. Si le script de migration échoue, la transaction sera annulée et la table de migration sera marquée comme complète et la version sera la version immédiatement précédente avant le script qui a causé l'erreur.

Pour activer cette fonctionnalité, vous devez appeler la méthode withTransactionEnabled en passant true comme paramètre :

<?php
$migration->withTransactionEnabled(true);

REMARQUE : Cette fonctionnalité n'est pas disponible pour MySQL car elle ne prend pas en charge les commandes DDL à l'intérieur d'une transaction. Si vous utilisez cette méthode avec MySQL, la migration l'ignorera silencieusement. Plus d'infos : https://dev.mysql.com/doc/refman/8.0/en/cannot-roll-back.html

Conseils pour écrire des migrations SQL pour Postgres

À propos de la création de déclencheurs et de fonctions SQL

-- DO
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
    BEGIN
        -- Vérifier que empname et salary sont fournis
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname ne peut pas être nul'; -- peu importe que ces commentaires soient vides ou non
        END IF; --
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% ne peut pas avoir de salaire nul', NEW.empname; --
        END IF; --

        -- Qui travaille pour nous quand ils doivent le payer ?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% ne peut pas avoir un salaire négatif', NEW.empname; --
        END IF; --

        -- Se souvenir de qui a changé la paie quand
        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
        -- Vérifier que empname et salary sont fournis
        IF NEW.empname IS NULL THEN
            RAISE EXCEPTION 'empname ne peut pas être nul';
        END IF;
        IF NEW.salary IS NULL THEN
            RAISE EXCEPTION '% ne peut pas avoir de salaire nul', NEW.empname;
        END IF;

        -- Qui travaille pour nous quand ils doivent le payer ?
        IF NEW.salary < 0 THEN
            RAISE EXCEPTION '% ne peut pas avoir un salaire négatif', NEW.empname;
        END IF;

        -- Se souvenir de qui a changé la paie quand
        NEW.last_date := current_timestamp;
        NEW.last_user := current_user;
        RETURN NEW;
    END;
$emp_stamp$ LANGUAGE plpgsql;

Puisque le niveau d'abstraction de base de données PDO ne peut pas exécuter des lots d'instructions SQL, lorsque byjg/migration lit un fichier de migration, il doit diviser tout le contenu du fichier SQL aux points-virgules et exécuter les instructions une par une. Cependant, il existe un type d'instruction qui peut avoir plusieurs points-virgules dans son corps : les fonctions.

Afin de pouvoir analyser les fonctions correctement, byjg/migration 2.1.0 a commencé à diviser les fichiers de migration à la séquence point-virgule + EOL au lieu de simplement le point-virgule. De cette façon, si vous ajoutez un commentaire vide après chaque point-virgule interne d'une définition de fonction, byjg/migration pourra l'analyser.

Malheureusement, si vous oubliez d'ajouter l'un de ces commentaires, la bibliothèque divisera l'instruction CREATE FUNCTION en plusieurs parties et la migration échouera.

Éviter le caractère deux-points (:)

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

Puisque PDO utilise le caractère deux-points pour préfixer les paramètres nommés dans les instructions préparées, son utilisation le perturbera dans d'autres contextes.

Par exemple, les instructions PostgreSQL peuvent utiliser :: pour convertir des valeurs entre types. D'autre part, PDO lira cela comme un paramètre nommé invalide dans un contexte invalide et échouera lorsqu'il essaiera de l'exécuter.

Le seul moyen de corriger cette incohérence est d'éviter complètement l'utilisation de double points (dans ce cas, PostgreSQL à également une syntaxe alternative : CAST(value AS type)).

Utiliser un éditeur SQL

Enfin, écrire des migrations SQL manuelles peut être épuisant, mais c'est beaucoup plus facile si vous utilisez un éditeur capable de comprendre la syntaxe SQL, d'offrir la saisie semi-automatique, d'explorer votre schéma de base de données actuel et/ou de reformater automatiquement votre code.

Gérer différentes migrations dans un seul schéma

Si vous devez créer différents scripts de migration et des versions dans le même schéma, cela est possible mais trop risqué et je ne recommande pas du tout.

Pour ce faire, vous devez créer différentes "tables de migration" en passant le paramètre au constructeur.

<?php
$migration = new \ByJG\DbMigration\Migration("db:/uri", "/path", true, "NEW_MIGRATION_TABLE_NAME");

Pour des raisons de sécurité, cette fonctionnalité n'est pas disponible en ligne de commande, mais vous pouvez utiliser la variable d'environnement MIGRATION_VERSION pour stocker le nom.

Nous recommandons vraiment de ne pas utiliser cette fonctionnalité. La recommandation est une migration pour un schéma.

Exécution des tests unitaires

Des tests unitaires de base peuvent être exécutés par :

vendor/bin/phpunit

Exécution des tests de base de données

Exécuter des tests d'intégration nécessite que vous ayez les bases de données en marche. Nous avons fourni un docker-compose.yml de base et vous pouvez l'utiliser pour démarrer les bases de données pour les tests.

Exécution des bases de données

docker-compose up -d postgres mysql mssql

Exécuter les tests

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*

En option, vous pouvez définir l'hôte et le mot de passe utilisés par les tests unitaires

export MYSQL_TEST_HOST=localhost     # par défaut localhost
export MYSQL_PASSWORD=newpassword    # utilisez '.' si vous souhaitez avoir un mot de passe nul
export PSQL_TEST_HOST=localhost      # par défaut localhost
export PSQL_PASSWORD=newpassword     # utilisez '.' si vous souhaitez avoir un mot de passe nul
export MSSQL_TEST_HOST=localhost     # par défaut localhost
export MSSQL_PASSWORD=Pa55word
export SQLITE_TEST_HOST=/tmp/test.db      # par défaut /tmp/test.db

Awesome-plugins/session

FlightPHP Session - Gestionnaire de sessions léger basé sur des fichiers

Ceci est un gestionnaire de sessions léger basé sur des fichiers, plugin pour le Flight PHP Framework. Il fournit une solution simple mais puissante pour gérer les sessions, avec des fonctionnalités comme des lectures de sessions non bloquantes, un chiffrement optionnel, une fonctionnalité d'auto-commit et un mode test pour le développement. Les données de session sont stockées dans des fichiers, ce qui en fait un choix idéal pour les applications qui n'ont pas besoin d'une base de données.

Si vous souhaitez utiliser une base de données, consultez le plugin ghostff/session qui propose de nombreuses fonctionnalités similaires mais avec un backend de base de données.

Visitez le dépôt Github pour le code source complet et les détails.

Installation

Installez le plugin via Composer :

composer require flightphp/session

Utilisation de base

Voici un exemple simple de l'utilisation du plugin flightphp/session dans votre application Flight :

require 'vendor/autoload.php';

use flight\Session;

$app = Flight::app();

// Enregistrer le service de session
$app->register('session', Session::class);

// Exemple de route avec utilisation de session
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'); // Affiche : johndoe
    echo $session->get('preferences', 'default_theme'); // Affiche : default_theme

    if ($session->get('user_id')) {
        Flight::json(['message' => 'L\'utilisateur est connecté !', 'user_id' => $session->get('user_id')]);
    }
});

Flight::route('/logout', function() {
    $session = Flight::session();
    $session->clear(); // Effacer toutes les données de session
    Flight::json(['message' => 'Déconnexion réussie']);
});

Flight::start();

Points clés

Configuration

Vous pouvez personnaliser le gestionnaire de sessions en passant un tableau d'options lors de l'enregistrement :

// Oui, c'est un double tableau :)
$app->register('session', Session::class, [ [
    'save_path' => '/custom/path/to/sessions',         // Répertoire pour les fichiers de session
    'prefix' => 'myapp_',                              // Préfixe pour les fichiers de session
    'encryption_key' => 'a-secure-32-byte-key-here',   // Activer le chiffrement (32 octets recommandés pour AES-256-CBC)
    'auto_commit' => false,                            // Désactiver l'auto-commit pour un contrôle manuel
    'start_session' => true,                           // Démarrer la session automatiquement (par défaut : true)
    'test_mode' => false,                              // Activer le mode test pour le développement
    'serialization' => 'json',                         // Méthode de sérialisation : 'json' (par défaut) ou 'php' (léguée)
] ]);

Options de configuration

Option Description Valeur par défaut
save_path Répertoire où les fichiers de session sont stockés sys_get_temp_dir() . '/flight_sessions'
prefix Préfixe pour le fichier de session enregistré sess_
encryption_key Clé pour le chiffrement AES-256-CBC (optionnel) null (pas de chiffrement)
auto_commit Enregistrement automatique des données de session à l'arrêt true
start_session Démarrer la session automatiquement true
test_mode Exécuter en mode test sans affecter les sessions PHP false
test_session_id ID de session personnalisé pour le mode test (optionnel) Généré aléatoirement s'il n'est pas défini
serialization Méthode de sérialisation : 'json' (par défaut, sécurisé) ou 'php' (léguée, permet les objets) 'json'

Modes de sérialisation

Par défaut, cette bibliothèque utilise la sérialisation JSON pour les données de session, ce qui est sécurisé et empêche les vulnérabilités d'injection d'objets PHP. Si vous devez stocker des objets PHP dans la session (non recommandé pour la plupart des applications), vous pouvez choisir la sérialisation PHP léguée :

Note : Si vous utilisez la sérialisation JSON, tenter de stocker un objet lèvera une exception.

Utilisation avancée

Commit manuel

Si vous désactivez l'auto-commit, vous devez manuellement enregistrer les modifications :

$app->register('session', Session::class, ['auto_commit' => false]);

Flight::route('/update', function() {
    $session = Flight::session();
    $session->set('key', 'value');
    $session->commit(); // Enregistrer explicitement les modifications
});

Sécurité des sessions avec chiffrement

Activez le chiffrement pour les données sensibles :

$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'); // Chiffré automatiquement
    echo $session->get('credit_card'); // Déchiffré lors de la récupération
});

Régénération de session

Régénérez l'ID de session pour des raisons de sécurité (par exemple, après la connexion) :

Flight::route('/post-login', function() {
    $session = Flight::session();
    $session->regenerate(); // Nouvel ID, conserver les données
    // OU
    $session->regenerate(true); // Nouvel ID, supprimer les anciennes données
});

Exemple de middleware

Protégez les routes avec une authentification basée sur les sessions :

Flight::route('/admin', function() {
    Flight::json(['message' => 'Bienvenue sur le panneau d\'administration']);
})->addMiddleware(function() {
    $session = Flight::session();
    if (!$session->get('is_admin')) {
        Flight::halt(403, 'Accès refusé');
    }
});

Ceci est juste un exemple simple de l'utilisation dans un middleware. Pour un exemple plus approfondi, consultez la documentation sur le middleware.

Méthodes

La classe Session fournit ces méthodes :

Toutes les méthodes sauf get() et id() renvoient l'instance Session pour la chaîne.

Pourquoi utiliser ce plugin ?

Détails techniques

Contribution

Les contributions sont les bienvenues ! Forkez le dépôt, apportez vos modifications et soumettez une pull request. Signalez les bogues ou suggérez des fonctionnalités via le tracker d'issues Github.

Licence

Ce plugin est sous licence MIT. Consultez le dépôt Github pour plus de détails.

Awesome-plugins/runway

Piste

Runway est une application CLI qui vous aide à gérer vos applications Flight. Il peut générer des contrôleurs, afficher toutes les routes, et bien plus encore. Il est basé sur l'excellente bibliothèque adhocore/php-cli.

Cliquez ici pour voir le code.

Installation

Installez avec composer.

composer require flightphp/runway

Configuration de Base

La première fois que vous exécutez Runway, il vous guidera à travers un processus de configuration et créera un fichier de configuration .runway.json à la racine de votre projet. Ce fichier contiendra certaines configurations nécessaires pour que Runway fonctionne correctement.

Utilisation

Runway dispose de plusieurs commandes que vous pouvez utiliser pour gérer votre application Flight. Il y a deux façons faciles d'utiliser Runway.

  1. Si vous utilisez le projet de base, vous pouvez exécuter php runway [commande] depuis la racine de votre projet.
  2. Si vous utilisez Runway comme un paquet installé via composer, vous pouvez exécuter vendor/bin/runway [commande] depuis la racine de votre projet.

Pour n'importe quelle commande, vous pouvez ajouter le drapeau --help pour obtenir plus d'informations sur comment utiliser la commande.

php runway routes --help

Voici quelques exemples :

Générer un Contrôleur

Selon la configuration dans votre fichier .runway.json, l'emplacement par défaut générera un contrôleur pour vous dans le répertoire app/controllers/.

php runway make:controller MyController

Générer un Modèle Active Record

Selon la configuration dans votre fichier .runway.json, l'emplacement par défaut générera un contrôleur pour vous dans le répertoire app/records/.

php runway make:record users

Par exemple, si vous avez la table users avec le schéma suivant : id, name, email, created_at, updated_at, un fichier similaire au suivant sera créé dans le fichier app/records/UserRecord.php :

<?php

declare(strict_types=1);

namespace app\records;

/**
 * Class ActiveRecord pour la table des utilisateurs.
 * @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
 * // vous pouvez également ajouter des relations ici une fois que vous les définissez dans le tableau $relations
 * @property CompanyRecord $company Exemple d'une relation
 */
class UserRecord extends \flight\ActiveRecord
{
    /**
     * @var array $relations Définir les relations pour le modèle
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [];

    /**
     * Constructeur
     * @param mixed $databaseConnection La connexion à la base de données
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

Afficher Toutes les Routes

Cela affichera toutes les routes actuellement enregistrées avec Flight.

php runway routes

Si vous souhaitez uniquement visualiser des routes spécifiques, vous pouvez ajouter un drapeau pour filtrer les routes.

# Afficher uniquement les routes GET
php runway routes --get

# Afficher uniquement les routes POST
php runway routes --post

# etc.

Personnaliser Runway

Si vous êtes en train de créer un paquet pour Flight, ou si vous voulez ajouter vos propres commandes personnalisées dans votre projet, vous pouvez le faire en créant un répertoire src/commands/, flight/commands/, app/commands/, ou commands/ pour votre projet/paquet.

Pour créer une commande, vous étendez simplement la classe AbstractBaseCommand et implémentez au minimum une méthode __construct et une méthode execute.

<?php

declare(strict_types=1);

namespace flight\commands;

class ExampleCommand extends AbstractBaseCommand
{
    /**
     * Constructeur
     *
     * @param array<string,mixed> $config Config JSON de .runway-config.json
     */
    public function __construct(array $config)
    {
        parent::__construct('make:example', 'Crée un exemple pour la documentation', $config);
        $this->argument('<funny-gif>', 'Le nom du gif drôle');
    }

    /**
     * Exécute la fonction
     *
     * @return void
     */
    public function execute(string $controller)
    {
        $io = $this->app()->io();

        $io->info('Création de l\'exemple...');

        // Faites quelque chose ici

        $io->ok('Exemple créé !');
    }
}

Consultez la Documentation de adhocore/php-cli pour plus d'informations sur la façon de créer vos propres commandes personnalisées dans votre application Flight !

Awesome-plugins/tracy_extensions

Extensions du panneau Tracy Flight

Ceci est un ensemble d'extensions pour rendre le travail avec Flight un peu plus riche.

Voici le panneau

Flight Bar

Et chaque panneau affiche des informations très utiles sur votre application !

Flight Data Flight Database Flight Request

Cliquez ici pour voir le code.

Installation

Exécutez composer require flightphp/tracy-extensions --dev et vous êtes prêt !

Configuration

Il y a très peu de configuration à faire pour démarrer cela. Vous devrez initialiser le débogueur Tracy avant d'utiliser cela https://tracy.nette.org/en/guide :

<?php

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

// code de démarrage
require __DIR__ . '/vendor/autoload.php';

Debugger::enable();
// Vous devrez peut-être spécifier votre environnement avec Debugger::enable(Debugger::DEVELOPMENT)

// si vous utilisez des connexions à la base de données dans votre application, il y a un 
// wrapper PDO requis à utiliser UNIQUEMENT EN DÉVELOPPEMENT (pas en production s'il vous plaît !)
// Il a les mêmes paramètres qu'une connexion PDO régulière
$pdo = new PdoQueryCapture('sqlite:test.db', 'user', 'pass');
// ou si vous l'attachez au framework Flight
Flight::register('db', PdoQueryCapture::class, ['sqlite:test.db', 'user', 'pass']);
// maintenant, chaque fois que vous exécutez une requête, elle capturera le temps, la requête et les paramètres

// Cela connecte les points
if(Debugger::$showBar === true) {
    // Cela doit être false sinon Tracy ne peut pas réellement rendre :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app());
}

// plus de code

Flight::start();

Configuration supplémentaire

Données de session

Si vous avez un gestionnaire de session personnalisé (comme ghostff/session), vous pouvez passer n'importe quel tableau de données de session à Tracy et il l'affichera automatiquement pour vous. Vous le passez avec la clé session_data dans le deuxième paramètre du constructeur TracyExtensionLoader.


use Ghostff\Session\Session;
// ou utilisez flight\Session;

require 'vendor/autoload.php';

$app = Flight::app();

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

if(Debugger::$showBar === true) {
    // Cela doit être false sinon Tracy ne peut pas réellement rendre :(
    Flight::set('flight.content_length', false);
    new TracyExtensionLoader(Flight::app(), [ 'session_data' => Flight::session()->getAll() ]);
}

// routes et autres choses...

Flight::start();

Latte

PHP 8.1+ est requis pour cette section.

Si vous avez Latte installé dans votre projet, Tracy a une intégration native avec Latte pour analyser vos templates. Vous enregistrez simplement l'extension avec votre instance Latte.


require 'vendor/autoload.php';

$app = Flight::app();

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

    // autres configurations...

    // n'ajouter l'extension que si la barre de débogage Tracy est activée
    if(Debugger::$showBar === true) {
        // c'est ici que vous ajoutez le panneau Latte à Tracy
        $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
    }

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

Awesome-plugins/apm

Documentation FlightPHP APM

Bienvenue dans FlightPHP APM—votre coach personnel de performance pour votre application ! Ce guide est votre feuille de route pour configurer, utiliser et maîtriser la Surveillance de la Performance des Applications (APM) avec FlightPHP. Que vous traquiez des requêtes lentes ou que vous souhaitiez simplement vous enthousiasmer pour des graphiques de latence, nous avons tout ce qu'il vous faut. Rendons votre application plus rapide, vos utilisateurs plus heureux, et vos sessions de débogage plus fluides !

Découvrez une démo du tableau de bord pour le site Flight Docs.

FlightPHP APM

Pourquoi l'APM est important

Imaginez ceci : votre application est un restaurant animé. Sans moyen de suivre la durée des commandes ou les endroits où la cuisine ralentit, vous devinez pourquoi les clients partent mécontents. L'APM est votre sous-chef—il surveille chaque étape, des requêtes entrantes aux requêtes de base de données, et signale tout ce qui vous ralentit. Les pages lentes font fuir les utilisateurs (des études disent que 53 % rebondissent si un site prend plus de 3 secondes à charger !), et l'APM vous aide à attraper ces problèmes avant qu'ils ne piquent. C'est une tranquillité d'esprit proactive—moins de moments « pourquoi c'est cassé ? », plus de victoires « regardez comme ça tourne bien ! ».

Installation

Commencez avec Composer :

composer require flightphp/apm

Vous aurez besoin de :

Bases de données prises en charge

FlightPHP APM prend actuellement en charge les bases de données suivantes pour stocker les métriques :

Vous pouvez choisir le type de base de données lors de l'étape de configuration (voir ci-dessous). Assurez-vous que votre environnement PHP a les extensions nécessaires installées (par exemple, pdo_sqlite ou pdo_mysql).

Pour commencer

Voici votre guide étape par étape vers l'excellence APM :

1. Enregistrer l'APM

Ajoutez ceci dans votre index.php ou un fichier services.php pour commencer le suivi :

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

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

// Si vous ajoutez une connexion à la base de données
// Doit être PdoWrapper ou PdoQueryCapture de Tracy Extensions
$pdo = new PdoWrapper('mysql:host=localhost;dbname=example', 'user', 'pass', null, true); // <-- True requis pour activer le suivi dans l'APM.
$Apm->addPdoConnection($pdo);

Que se passe-t-il ici ?

Astuce Pro : Échantillonnage Si votre application est occupée, logger chaque requête pourrait surcharger les choses. Utilisez un taux d'échantillonnage (0.0 à 1.0) :

$Apm = new Apm($ApmLogger, 0.1); // Logue 10 % des requêtes

Cela garde les performances vives tout en vous donnant des données solides.

2. Le configurer

Exécutez ceci pour créer votre .runway-config.json :

php vendor/bin/runway apm:init

Que fait ceci ?

Ce processus demandera également si vous voulez exécuter les migrations pour cette configuration. Si vous l'installez pour la première fois, la réponse est oui.

Pourquoi deux emplacements ? Les métriques brutes s'accumulent vite (pensez à des logs non filtrés). Le worker les traite en une destination structurée pour le tableau de bord. Cela garde les choses en ordre !

3. Traiter les métriques avec le Worker

Le worker transforme les métriques brutes en données prêtes pour le tableau de bord. Exécutez-le une fois :

php vendor/bin/runway apm:worker

Que fait-il ?

Le garder en cours Pour les applications en direct, vous voudrez un traitement continu. Voici vos options :

Pourquoi s'embêter ? Sans le worker, votre tableau de bord est vide. C'est le pont entre les logs bruts et les insights actionnables.

4. Lancer le Tableau de Bord

Voyez les signes vitaux de votre application :

php vendor/bin/runway apm:dashboard

Qu'est-ce que c'est ?

Le personnaliser :

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

Ouvrez l'URL dans votre navigateur et explorez !

Mode Production

Pour la production, vous devrez peut-être essayer quelques techniques pour faire tourner le tableau de bord, car il y a probablement des pare-feu et d'autres mesures de sécurité en place. Voici quelques options :

Voulez-vous un tableau de bord différent ?

Vous pouvez construire votre propre tableau de bord si vous le souhaitez ! Regardez le répertoire vendor/flightphp/apm/src/apm/presenter pour des idées sur la façon de présenter les données pour votre propre tableau de bord !

Fonctionnalités du Tableau de Bord

Le tableau de bord est votre quartier général APM—voici ce que vous verrez :

Extras :

Exemple : Une requête vers /users pourrait montrer :

Ajout d'Événements Personnalisés

Suivez n'importe quoi—comme un appel API ou un processus de paiement :

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

Où apparaît-il ? Dans les détails de requête du tableau de bord sous « Événements Personnalisés »—extensible avec un formatage JSON joli.

Cas d'Utilisation :

$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
]));

Maintenant, vous verrez si cet API ralentit votre application !

Surveillance de la Base de Données

Suivez les requêtes PDO comme ceci :

use flight\database\PdoWrapper;

$pdo = new PdoWrapper('sqlite:/path/to/db.sqlite', null, null, null, true); // <-- True requis pour activer le suivi dans l'APM.
$Apm->addPdoConnection($pdo);

Ce Que Vous Obtenez :

Attention :

Exemple de Sortie :

Options du Worker

Ajustez le worker à votre goût :

Exemple :

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

Tourne pendant une heure, traitant 100 métriques à la fois.

ID de Requête dans l'Application

Chaque requête a un ID de requête unique pour le suivi. Vous pouvez utiliser cet ID dans votre application pour corréler les logs et les métriques. Par exemple, vous pouvez ajouter l'ID de requête à une page d'erreur :

Flight::map('error', function($message) {
    // Obtenez l'ID de requête depuis l'en-tête de réponse X-Flight-Request-Id
    $requestId = Flight::response()->getHeader('X-Flight-Request-Id');

    // De plus, vous pourriez le récupérer depuis la variable Flight
    // Cette méthode ne fonctionnera pas bien sur swoole ou d'autres plateformes asynchrones.
    // $requestId = Flight::get('apm.request_id');

    echo "Erreur : $message (ID de Requête : $requestId)";
});

Mise à Niveau

Si vous mettez à niveau vers une version plus récente de l'APM, il est possible qu'il y ait des migrations de base de données à exécuter. Vous pouvez le faire en exécutant la commande suivante :

php vendor/bin/runway apm:migrate

Ceci exécutera toutes les migrations nécessaires pour mettre à jour le schéma de la base de données vers la dernière version.

Note : Si votre base de données APM est grande en taille, ces migrations pourraient prendre un certain temps à s'exécuter. Vous pourriez vouloir exécuter cette commande pendant les heures creuses.

Purgage des Anciennes Données

Pour garder votre base de données en ordre, vous pouvez purger les anciennes données. C'est particulièrement utile si vous exécutez une application occupée et que vous voulez garder la taille de la base de données gérable. Vous pouvez le faire en exécutant la commande suivante :

php vendor/bin/runway apm:purge

Ceci supprimera toutes les données plus anciennes que 30 jours de la base de données. Vous pouvez ajuster le nombre de jours en passant une valeur différente à l'option --days :

php vendor/bin/runway apm:purge --days 7

Ceci supprimera toutes les données plus anciennes que 7 jours de la base de données.

Dépannage

Coincé ? Essayez ceci :

Awesome-plugins/tracy

Tracy

Tracy est un gestionnaire d'erreurs incroyable qui peut être utilisé avec Flight. Il dispose de plusieurs panneaux qui peuvent vous aider à déboguer votre application. Il est également très facile à étendre et à ajouter vos propres panneaux. L'équipe de Flight a créé quelques panneaux spécifiquement pour les projets Flight avec le plugin flightphp/tracy-extensions.

Installation

Installez avec composer. Et vous voudrez en fait installer ceci sans la version dev car Tracy est livré avec un composant de gestion des erreurs de production.

composer require tracy/tracy

Configuration de base

Il existe quelques options de configuration de base pour commencer. Vous pouvez en savoir plus à leur sujet dans la Documentation de Tracy.


require 'vendor/autoload.php';

use Tracy\Debugger;

// Activer Tracy
Debugger::enable();
// Debugger::enable(Debugger::DEVELOPMENT) // parfois vous devez être explicite (également Debugger::PRODUCTION)
// Debugger::enable('23.75.345.200'); // vous pouvez également fournir un tableau d'adresses IP

// C'est là que les erreurs et exceptions seront journalisées. Assurez-vous que ce répertoire existe et est accessible en écriture.
Debugger::$logDirectory = __DIR__ . '/../log/';
Debugger::$strictMode = true; // afficher toutes les erreurs
// Debugger::$strictMode = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED; // toutes les erreurs sauf les avis obsolètes
if (Debugger::$showBar) {
    $app->set('flight.content_length', false); // si la barre de débogage est visible, alors la longueur du contenu ne peut pas être définie par Flight

    // Ceci est spécifique à l'extension Tracy pour Flight si vous l'avez incluse
    // sinon mettez en commentaire.
    new TracyExtensionLoader($app);
}

Conseils utiles

Lorsque vous déboguez votre code, il y a quelques fonctions très utiles pour afficher des données pour vous.

Awesome-plugins/active_record

Flight Active Record

Un enregistrement actif est une cartographie d'une entité de base de données à un objet PHP. En d'autres termes, si vous avez une table utilisateurs dans votre base de données, vous pouvez "traduire" une ligne dans cette table en une classe User et un objet $user dans votre code. Voir exemple de base.

Cliquez ici pour le dépôt sur GitHub.

Exemple de Base

Assumons que vous ayez la table suivante :

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

Maintenant, vous pouvez configurer une nouvelle classe pour représenter cette table :

/**
 * Une classe ActiveRecord est généralement singulière
 * 
 * Il est fortement recommandé d'ajouter ici les propriétés de la table en tant que commentaires
 * 
 * @property int    $id
 * @property string $name
 * @property string $password
 */ 
class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        // vous pouvez le définir de cette manière
        parent::__construct($database_connection, 'users');
        // ou de cette manière
        parent::__construct($database_connection, null, [ 'table' => 'users']);
    }
}

Maintenant, regardez la magie opérer !

// pour sqlite
$database_connection = new PDO('sqlite:test.db'); // ceci est juste un exemple, vous devriez probablement utiliser une vraie connexion à une base de données

// pour mysql
$database_connection = new PDO('mysql:host=localhost;dbname=test_db&charset=utf8bm4', 'username', 'password');

// ou mysqli
$database_connection = new mysqli('localhost', 'username', 'password', 'test_db');
// ou mysqli avec création non basée sur des objets
$database_connection = mysqli_connect('localhost', 'username', 'password', 'test_db');

$user = new User($database_connection);
$user->name = 'Bobby Tables';
$user->password = password_hash('some cool password');
$user->insert();
// ou $user->save();

echo $user->id; // 1

$user->name = 'Joseph Mamma';
$user->password = password_hash('some cool password again!!!');
$user->insert();
// impossible d'utiliser $user->save() ici sinon il pensera que c'est une mise à jour !

echo $user->id; // 2

Et c'était aussi simple que cela d'ajouter un nouvel utilisateur ! Maintenant qu'il y a une ligne d'utilisateur dans la base de données, comment la récupérer ?

$user->find(1); // trouver id = 1 dans la base de données et le renvoyer.
echo $user->name; // 'Bobby Tables'

Et que se passe-t-il si vous souhaitez trouver tous les utilisateurs ?

$users = $user->findAll();

Que dire d'une certaine condition ?

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

Voyez comme c'est amusant ? Installons-le et commençons !

Installation

Il suffit d'installer avec Composer

composer require flightphp/active-record 

Usage

Cela peut être utilisé comme une bibliothèque autonome ou avec le Framework PHP Flight. Complètement à vous de choisir.

Autonome

Il suffit de vous assurer que vous passez une connexion PDO au constructeur.

$pdo_connection = new PDO('sqlite:test.db'); // ceci est juste un exemple, vous devriez probablement utiliser une vraie connexion à une base de données

$User = new User($pdo_connection);

Vous ne voulez pas toujours définir votre connexion à la base de données dans le constructeur ? Consultez Gestion des Connexions à la Base de Données pour d'autres idées !

Enregistrer en tant que méthode dans Flight

Si vous utilisez le Framework PHP Flight, vous pouvez enregistrer la classe ActiveRecord en tant que service, mais vous n'êtes vraiment pas obligé.

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

// puis vous pouvez l'utiliser comme ceci dans un contrôleur, une fonction, etc.

Flight::user()->find(1);

Méthodes runway

runway est un outil CLI pour Flight qui a une commande personnalisée pour cette bibliothèque.

# Usage
php runway make:record database_table_name [class_name]

# Exemple
php runway make:record users

Cela créera une nouvelle classe dans le répertoire app/records/ sous le nom UserRecord.php avec le contenu suivant :

<?php

declare(strict_types=1);

namespace app\records;

/**
 * Classe ActiveRecord pour la table des utilisateurs.
 * @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 Définir les relations pour le modèle
     *   https://docs.flightphp.com/awesome-plugins/active-record#relationships
     */
    protected array $relations = [
        // 'relation_name' => [ self::HAS_MANY, 'RelatedClass', 'foreign_key' ],
    ];

    /**
     * Constructeur
     * @param mixed $databaseConnection La connexion à la base de données
     */
    public function __construct($databaseConnection)
    {
        parent::__construct($databaseConnection, 'users');
    }
}

Fonctions CRUD

find($id = null) : boolean|ActiveRecord

Trouver un enregistrement et l'assigner à l'objet actuel. Si vous passez un $id de quelque nature que ce soit, il effectuera une recherche sur la clé primaire avec cette valeur. Si rien n'est passé, il trouvera simplement le premier enregistrement dans la table.

De plus, vous pouvez lui passer d'autres méthodes d'aide pour interroger votre table.

// trouver un enregistrement avec certaines conditions au préalable
$user->notNull('password')->orderBy('id DESC')->find();

// trouver un enregistrement par un id spécifique
$id = 123;
$user->find($id);

findAll(): array<int,ActiveRecord>

Trouve tous les enregistrements dans la table que vous spécifiez.

$user->findAll();

isHydrated(): boolean (v0.4.0)

Retourne true si l'enregistrement actuel a été hydraté (récupéré de la base de données).

$user->find(1);
// si un enregistrement est trouvé avec des données...
$user->isHydrated(); // true

insert(): boolean|ActiveRecord

Insère l'enregistrement actuel dans la base de données.

$user = new User($pdo_connection);
$user->name = 'demo';
$user->password = md5('demo');
$user->insert();
Clés Primaires Basées sur du Texte

Si vous avez une clé primaire basée sur du texte (comme un UUID), vous pouvez définir la valeur de la clé primaire avant d'insérer de deux manières.

$user = new User($pdo_connection, [ 'primaryKey' => 'uuid' ]);
$user->uuid = 'some-uuid';
$user->name = 'demo';
$user->password = md5('demo');
$user->insert(); // ou $user->save();

ou vous pouvez laisser la clé primaire être générée automatiquement pour vous via des événements.

class User extends flight\ActiveRecord {
    public function __construct($database_connection)
    {
        parent::__construct($database_connection, 'users', [ 'primaryKey' => 'uuid' ]);
        // vous pouvez également définir la primaryKey de cette manière au lieu de l'array ci-dessus.
        $this->primaryKey = 'uuid';
    }

    protected function beforeInsert(self $self) {
        $self->uuid = uniqid(); // ou comme vous avez besoin de générer vos identifiants uniques
    }
}

Si vous ne définissez pas la clé primaire avant d'insérer, elle sera définie sur le rowid et la base de données la générera pour vous, mais elle ne sera pas persistante car ce champ peut ne pas exister dans votre table. C'est pourquoi il est recommandé d'utiliser l'événement pour gérer cela automatiquement.

update(): boolean|ActiveRecord

Met à jour l'enregistrement actuel dans la base de données.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@example.com';
$user->update();

save(): boolean|ActiveRecord

Insère ou met à jour l'enregistrement actuel dans la base de données. Si l'enregistrement a un id, il sera mis à jour, sinon il sera inséré.

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

Remarque : Si vous avez des relations définies dans la classe, elles seront sauvegardées de manière récursive si elles ont été définies, instanciées et ont des données "sales" à mettre à jour. (v0.4.0 et plus)

delete(): boolean

Supprime l'enregistrement actuel de la base de données.

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

Vous pouvez également supprimer plusieurs enregistrements en exécutant une recherche au préalable.

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

dirty(array $dirty = []): ActiveRecord

Les données "sales" se réfèrent aux données qui ont été modifiées dans un enregistrement.

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

// rien n'est "sale" à ce stade.

$user->email = 'test@example.com'; // maintenant l'email est considéré comme "sale" puisqu'il a changé.
$user->update();
// maintenant il n'y a pas de données qui sont sales car elles ont été mises à jour et persistées dans la base de données

$user->password = password_hash()'newpassword'); // maintenant c'est sale
$user->dirty(); // ne rien passer effacera toutes les entrées sales.
$user->update(); // rien ne sera mis à jour car rien n'a été capturé comme sale.

$user->dirty([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // à la fois le nom et le mot de passe sont mis à jour.

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

Ceci est un alias pour la méthode dirty(). C'est un peu plus clair ce que vous faites.

$user->copyFrom([ 'name' => 'something', 'password' => password_hash('a different password') ]);
$user->update(); // à la fois le nom et le mot de passe sont mis à jour.

isDirty(): boolean (v0.4.0)

Retourne true si l'enregistrement actuel a été modifié.

$user->greaterThan('id', 0)->orderBy('id desc')->find();
$user->email = 'test@email.com';
$user->isDirty(); // true

reset(bool $include_query_data = true): ActiveRecord

Réinitialise l'enregistrement actuel à son état initial. C'est vraiment bon à utiliser dans des comportements de type boucle. Si vous passez true, il réinitialisera également les données de requête qui ont été utilisées pour trouver l'objet actuel (comportement par défaut).

$users = $user->greaterThan('id', 0)->orderBy('id desc')->find();
$user_company = new UserCompany($pdo_connection);

foreach($users as $user) {
    $user_company->reset(); // commencez avec une ardoise propre
    $user_company->user_id = $user->id;
    $user_company->company_id = $some_company_id;
    $user_company->insert();
}

getBuiltSql(): string (v0.4.1)

Après avoir exécuté une méthode find(), findAll(), insert(), update() ou save(), vous pouvez obtenir le SQL qui a été construit et l'utiliser à des fins de débogage.

Méthodes de Requête SQL

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

Vous pouvez sélectionner uniquement quelques-unes des colonnes d'une table si vous le souhaitez (c'est plus performant sur des tables très larges avec de nombreuses colonnes)

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

from(string $table)

Vous pouvez techniquement choisir une autre table aussi ! Pourquoi pas ?!

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

join(string $table_name, string $join_condition)

Vous pouvez même joindre à une autre table dans la base de données.

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

where(string $where_conditions)

Vous pouvez définir quelques arguments where personnalisés (vous ne pouvez pas définir de paramètres dans cette déclaration where)

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

Remarque de Sécurité - Vous pourriez être tenté de faire quelque chose comme $user->where("id = '{$id}' AND name = '{$name}'")->find();. S'il vous plaît, NE FAITES PAS CELA !!! Cela est susceptible de ce qu'on appelle des attaques par injection SQL. Il existe de nombreux articles en ligne, s'il vous plaît recherchez "sql injection attacks php" et vous trouverez beaucoup d'articles sur ce sujet. La manière appropriée de gérer cela avec cette bibliothèque est qu'au lieu de cette méthode where(), vous feriez quelque chose de plus comme $user->eq('id', $id)->eq('name', $name)->find(); Si vous devez absolument faire cela, la bibliothèque PDO a $pdo->quote($var) pour l'échapper pour vous. Ce n'est qu'après avoir utilisé quote() que vous pouvez l'utiliser dans une déclaration where().

group(string $group_by_statement)/groupBy(string $group_by_statement)

Groupez vos résultats par une condition particulière.

$user->select('COUNT(*) as count')->groupBy('name')->findAll();

order(string $order_by_statement)/orderBy(string $order_by_statement)

Trier la requête retournée d'une certaine manière.

$user->orderBy('name DESC')->find();

limit(string $limit)/limit(int $offset, int $limit)

Limitez le nombre d'enregistrements retournés. Si un second int est donné, il sera décalé, limite tout comme en SQL.

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

CONDITIONS WHERE

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

field = $value

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

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

field <> $value

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

isNull(string $field)

field IS NULL

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

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

field IS NOT NULL

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

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

field > $value

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

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

field < $value

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

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

field >= $value

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

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

field <= $value

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

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

field LIKE $value ou field NOT LIKE $value

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

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

field IN($value) ou field NOT IN($value)

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

between(string $field, array $values)

field BETWEEN $value AND $value1

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

Conditions OR

Il est possible d'envelopper vos conditions dans une déclaration OR. Cela se fait soit avec les méthodes startWrap() et endWrap(), soit en remplissant le 3ème paramètre de la condition après le champ et la valeur.

// Méthode 1
$user->eq('id', 1)->startWrap()->eq('name', 'demo')->or()->eq('name', 'test')->endWrap('OR')->find();
// Cela s'évaluera à `id = 1 AND (name = 'demo' OR name = 'test')`

// Méthode 2
$user->eq('id', 1)->eq('name', 'demo', 'OR')->find();
// Cela s'évaluera à `id = 1 OR name = 'demo'`

Relations

Vous pouvez définir plusieurs types de relations à l'aide de cette bibliothèque. Vous pouvez établir des relations un->plusieurs et un->un entre des tables. Cela nécessite une petite configuration supplémentaire dans la classe au préalable.

Définir le tableau $relations n'est pas difficile, mais deviner la syntaxe correcte peut être déroutant.

protected array $relations = [
    // vous pouvez nommer la clé comme bon vous semble. Le nom de l'ActiveRecord est probablement bon. Ex : user, contact, client
    'user' => [
        // requis
        // self::HAS_MANY, self::HAS_ONE, self::BELONGS_TO
        self::HAS_ONE, // c'est le type de relation

        // requis
        'Some_Class', // c'est la classe ActiveRecord "autre" à laquelle cela fera référence

        // requis
        // selon le type de relation
        // self::HAS_ONE = la clé étrangère qui référence la jointure
        // self::HAS_MANY = la clé étrangère qui référence la jointure
        // self::BELONGS_TO = la clé locale qui référence la jointure
        'local_or_foreign_key',
        // juste pour info, cela ne joint également qu'à la clé primaire du modèle "autre"

        // optionnel
        [ 'eq' => [ 'client_id', 5 ], 'select' => 'COUNT(*) as count', 'limit' 5 ], // conditions supplémentaires que vous souhaitez lors de la jointure de la relation
        // $record->eq('client_id', 5)->select('COUNT(*) as count')->limit(5))

        // optionnel
        'back_reference_name' // c'est si vous voulez faire référence à cette relation à elle-même Ex : $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');
    }
}

Maintenant, nous avons les références configurées afin que nous puissions les utiliser très facilement !

$user = new User($pdo_connection);

// trouver l'utilisateur le plus récent.
$user->notNull('id')->orderBy('id desc')->find();

// obtenir les contacts en utilisant la relation :
foreach($user->contacts as $contact) {
    echo $contact->id;
}

// ou nous pouvons aller dans l'autre sens.
$contact = new Contact();

// trouver un contact
$contact->find();

// obtenir l'utilisateur en utilisant la relation :
echo $contact->user->name; // c'est le nom de l'utilisateur

Assez cool, non ?

Définir des Données Personnalisées

Parfois, vous pourriez avoir besoin d'attacher quelque chose d'unique à votre ActiveRecord, comme un calcul personnalisé qui serait plus facile à attacher à l'objet qui serait ensuite passé à un modèle, par exemple.

setCustomData(string $field, mixed $value)

Vous attachez les données personnalisées avec la méthode setCustomData().

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

Et ensuite, vous le référencez simplement comme une propriété d'objet normale.

echo $user->page_view_count;

Événements

Une autre fonctionnalité super géniale de cette bibliothèque concerne les événements. Les événements sont déclenchés à certains moments en fonction de certaines méthodes que vous appelez. Ils sont très utiles pour vous aider à configurer des données automatiquement.

onConstruct(ActiveRecord $ActiveRecord, array &config)

Cela est vraiment utile si vous avez besoin de définir une connexion par défaut ou quelque chose comme cela.

// index.php ou bootstrap.php
Flight::register('db', 'PDO', [ 'sqlite:test.db' ]);

//
//
//

// User.php
class User extends flight\ActiveRecord {

    protected function onConstruct(self $self, array &$config) { // n'oubliez pas la référence &
        // vous pourriez faire cela pour définir automatiquement la connexion
        $config['connection'] = Flight::db();
        // ou ceci
        $self->transformAndPersistConnection(Flight::db());

        // Vous pouvez également définir le nom de la table de cette manière.
        $config['table'] = 'users';
    } 
}

beforeFind(ActiveRecord $ActiveRecord)

Cela est probablement seulement utile si vous avez besoin d'une manipulation de requête chaque fois.

class User extends flight\ActiveRecord {

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

    protected function beforeFind(self $self) {
        // toujours exécuter id >= 0 si c'est votre truc
        $self->gte('id', 0); 
    } 
}

afterFind(ActiveRecord $ActiveRecord)

Celui-ci est probablement plus utile si vous devez toujours exécuter une logique chaque fois que cet enregistrement est récupéré. Avez-vous besoin de déchiffrer quelque chose ? Avez-vous besoin d'exécuter une requête de comptage personnalisée chaque fois (pas performant mais peu importe) ?

class User extends flight\ActiveRecord {

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

    protected function afterFind(self $self) {
        // déchiffrer quelque chose
        $self->secret = yourDecryptFunction($self->secret, $some_key);

        // peut-être stocker quelque chose de personnalisé, comme une requête ???
        $self->setCustomData('view_count', $self->select('COUNT(*) count')->from('user_views')->eq('user_id', $self->id)['count']; 
    } 
}

beforeFindAll(ActiveRecord $ActiveRecord)

Cela est probablement seulement utile si vous avez besoin d'une manipulation de requête chaque fois.

class User extends flight\ActiveRecord {

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

    protected function beforeFindAll(self $self) {
        // toujours exécuter id >= 0 si c'est votre truc
        $self->gte('id', 0); 
    } 
}

afterFindAll(array<int,ActiveRecord> $results)

Semblable à afterFind() mais vous pouvez le faire pour tous les enregistrements à la place !

class User extends flight\ActiveRecord {

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

    protected function afterFindAll(array $results) {

        foreach($results as $self) {
            // faites quelque chose de cool comme aprèsFind()
        }
    } 
}

beforeInsert(ActiveRecord $ActiveRecord)

Vraiment utile si vous avez besoin de définir des valeurs par défaut à chaque fois.

class User extends flight\ActiveRecord {

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

    protected function beforeInsert(self $self) {
        // définissez des valeurs par défaut
        if(!$self->created_date) {
            $self->created_date = gmdate('Y-m-d');
        }

        if(!$self->password) {
            $self->password = password_hash((string) microtime(true));
        }
    } 
}

afterInsert(ActiveRecord $ActiveRecord)

Peut-être que vous avez un cas d'utilisation pour changer des données après leur insertion ?

class User extends flight\ActiveRecord {

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

    protected function afterInsert(self $self) {
        // vous faites comme bon vous semble
        Flight::cache()->set('most_recent_insert_id', $self->id);
        // ou quoi que ce soit d'autre....
    } 
}

beforeUpdate(ActiveRecord $ActiveRecord)

Vraiment utile si vous avez besoin de définir des valeurs par défaut chaque fois qu'une mise à jour a lieu.

class User extends flight\ActiveRecord {

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

    protected function beforeInsert(self $self) {
        // définir des valeurs par défaut
        if(!$self->updated_date) {
            $self->updated_date = gmdate('Y-m-d');
        }
    } 
}

afterUpdate(ActiveRecord $ActiveRecord)

Peut-être que vous avez un cas d'utilisation pour changer des données après qu'elles aient été mises à jour ?

class User extends flight\ActiveRecord {

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

    protected function afterInsert(self $self) {
        // vous faites comme bon vous semble
        Flight::cache()->set('most_recently_updated_user_id', $self->id);
        // ou quoi que ce soit d'autre....
    } 
}

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

Ceci est utile si vous souhaitez que des événements se produisent lorsque des insertions ou des mises à jour ont lieu. Je vous épargne une longue explication, mais je suis sûr que vous pouvez deviner ce que c'est.

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)

Je ne suis pas sûr de ce que vous voudriez faire ici, mais pas de jugement ! Foncez !

class User extends flight\ActiveRecord {

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

    protected function beforeDelete(self $self) {
        echo 'Il était un brave soldat... :cry-face:';
    } 
}

Gestion des Connexions à la Base de Données

Lorsque vous utilisez cette bibliothèque, vous pouvez définir la connexion à la base de données de plusieurs manières. Vous pouvez définir la connexion dans le constructeur, vous pouvez la définir via une variable de configuration $config['connection'] ou vous pouvez la définir via setDatabaseConnection() (v0.4.1).

$pdo_connection = new PDO('sqlite:test.db'); // par exemple
$user = new User($pdo_connection);
// ou
$user = new User(null, [ 'connection' => $pdo_connection ]);
// ou
$user = new User();
$user->setDatabaseConnection($pdo_connection);

Si vous souhaitez éviter de toujours définir un $database_connection chaque fois que vous appelez un enregistrement actif, il existe des moyens d'y parvenir !

// index.php ou bootstrap.php
// Réglez cela en tant que classe enregistrée dans 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);
    }
}

// Et maintenant, aucun argument requis !
$user = new User();

Remarque : Si vous prévoyez des tests unitaires, faire de cette façon peut ajouter quelques défis aux tests unitaires, mais globalement parce que vous pouvez injecter votre connexion avec setDatabaseConnection() ou $config['connection'], cela n'est pas trop mauvais.

Si vous devez rafraîchir la connexion à la base de données, par exemple si vous exécutez un script CLI longue durée et devez rafraîchir la connexion de temps en temps, vous pouvez réinitialiser la connexion avec $your_record->setDatabaseConnection($pdo_connection).

Contribuer

S'il vous plaît le faire. :D

Configuration

Lorsque vous contribuez, assurez-vous d'exécuter composer test-coverage pour maintenir une couverture de test de 100 % (ceci n'est pas une véritable couverture de test unitaire, plus comme un test d'intégration).

Assurez-vous également d'exécuter composer beautify et composer phpcs pour corriger les erreurs de linting.

Licence

MIT

Awesome-plugins/latte

Latte

Latte est un moteur de templating complet qui est très facile à utiliser et qui ressemble plus à une syntaxe PHP que Twig ou Smarty. Il est également très facile à étendre et à ajouter vos propres filtres et fonctions.

Installation

Installez avec composer.

composer require latte/latte

Configuration de base

Il existe quelques options de configuration de base pour commencer. Vous pouvez en lire plus à leur sujet dans la Documentation Latte.


require 'vendor/autoload.php';

$app = Flight::app();

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

    // Où latte stocke spécifiquement son cache
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    $finalPath = Flight::get('flight.views.path') . $template;

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

Exemple simple de layout

Voici un exemple simple d'un fichier de layout. C'est le fichier qui sera utilisé pour envelopper toutes vos autres vues.

<!-- app/views/layout.latte -->
<!doctype html>
<html lang="en">
    <head>
        <title>{$title ? $title . ' - '}Mon App</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <header>
            <nav>
                <!-- vos éléments de navigation ici -->
            </nav>
        </header>
        <div id="content">
            <!-- C'est la magie ici -->
            {block content}{/block}
        </div>
        <div id="footer">
            &copy; Copyright
        </div>
    </body>
</html>

Et maintenant, nous avons votre fichier qui va s'afficher à l'intérieur de ce bloc de contenu :

<!-- app/views/home.latte -->
<!-- Ceci indique à Latte que ce fichier est "à l'intérieur" du fichier layout.latte -->
{extends layout.latte}

<!-- C'est le contenu qui sera affiché à l'intérieur du layout dans le bloc de contenu -->
{block content}
    <h1>Page d'accueil</h1>
    <p>Bienvenue dans mon app !</p>
{/block}

Puis, lorsque vous allez afficher cela dans votre fonction ou votre contrôleur, vous feriez quelque chose comme ceci :

// route simple
Flight::route('/', function () {
    Flight::render('home.latte', [
        'title' => 'Page d\'accueil'
    ]);
});

// ou si vous utilisez un contrôleur
Flight::route('/', [HomeController::class, 'index']);

// HomeController.php
class HomeController
{
    public function index()
    {
        Flight::render('home.latte', [
            'title' => 'Page d\'accueil'
        ]);
    }
}

Consultez la Documentation Latte pour plus d'informations sur la façon d'utiliser Latte à son plein potentiel !

Débogage avec Tracy

PHP 8.1+ est requis pour cette section.

Vous pouvez également utiliser Tracy pour vous aider à déboguer vos fichiers de template Latte directement ! Si vous avez déjà installé Tracy, vous devez ajouter l'extension Latte à Tracy.

// services.php
use Tracy\Debugger;

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

    // Où latte stocke spécifiquement son cache
    $latte->setTempDirectory(__DIR__ . '/../cache/');

    $finalPath = Flight::get('flight.views.path') . $template;

    // Ceci n'ajoutera l'extension que si la barre de débogage Tracy est activée
    if (Debugger::$showBar === true) {
        // c'est ici que vous ajoutez le panneau Latte à Tracy
        $latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
    }
    $latte->render($finalPath, $data, $block);
});

Awesome-plugins/awesome_plugins

Plugins Géniaux

Flight est incroyablement extensible. Il existe un certain nombre de plugins qui peuvent être utilisés pour ajouter des fonctionnalités à votre application Flight. Certains sont officiellement supportés par l'équipe Flight et d'autres sont des bibliothèques micro/lite pour vous aider à démarrer.

Documentation API

La documentation API est cruciale pour toute API. Elle aide les développeurs à comprendre comment interagir avec votre API et ce qu'ils peuvent attendre en retour. Il existe quelques outils disponibles pour vous aider à générer la documentation API pour vos projets Flight.

Surveillance des Performances des Applications (APM)

La surveillance des performances des applications (APM) est cruciale pour toute application. Elle vous aide à comprendre comment votre application performe et où se trouvent les goulots d'étranglement. Il existe un certain nombre d'outils APM qui peuvent être utilisés avec Flight.

Asynchrone

Flight est déjà un framework rapide, mais lui ajouter un turbo le rend encore plus amusant (et challenging) !

Autorisation/Permissions

L'autorisation et les permissions sont cruciales pour toute application qui nécessite des contrôles sur qui peut accéder à quoi.

Mise en Cache

La mise en cache est une excellente façon d'accélérer votre application. Il existe un certain nombre de bibliothèques de mise en cache qui peuvent être utilisées avec Flight.

CLI

Les applications CLI sont une excellente façon d'interagir avec votre application. Vous pouvez les utiliser pour générer des contrôleurs, afficher toutes les routes, et plus encore.

Cookies

Les cookies sont une excellente façon de stocker de petites quantités de données côté client. Ils peuvent être utilisés pour stocker les préférences utilisateur, les paramètres d'application, et plus encore.

Débogage

Le débogage est crucial lorsque vous développez dans votre environnement local. Il existe quelques plugins qui peuvent améliorer votre expérience de débogage.

Bases de Données

Les bases de données sont au cœur de la plupart des applications. C'est ainsi que vous stockez et récupérez les données. Certaines bibliothèques de bases de données sont simplement des wrappers pour écrire des requêtes et d'autres sont des ORMs complets.

Chiffrement

Le chiffrement est crucial pour toute application qui stocke des données sensibles. Chiffrer et déchiffrer les données n'est pas terriblement difficile, mais stocker correctement la clé de chiffrement peut être difficile. La chose la plus importante est de ne jamais stocker votre clé de chiffrement dans un répertoire public ou de l'engager dans votre dépôt de code.

File d'Attente de Tâches

Les files d'attente de tâches sont vraiment utiles pour traiter les tâches de manière asynchrone. Cela peut être l'envoi d'emails, le traitement d'images, ou tout ce qui n'a pas besoin d'être fait en temps réel.

Session

Les sessions ne sont pas vraiment utiles pour les API, mais pour construire une application web, les sessions peuvent être cruciales pour maintenir l'état et les informations de connexion.

Modélisation

La modélisation est au cœur de toute application web avec une interface utilisateur. Il existe un certain nombre de moteurs de modélisation qui peuvent être utilisés avec Flight.

Intégration WordPress

Vous voulez utiliser Flight dans votre projet WordPress ? Il y a un plugin pratique pour cela !

Contribution

Vous avez un plugin que vous aimeriez partager ? Soumettez une pull request pour l'ajouter à la liste !

Media

Médias

Nous avons essayé de recenser ce que nous pouvions des différents types de médias disponibles sur Internet concernant Flight. Voir ci-dessous pour différents ressources que vous pouvez utiliser pour en apprendre plus sur Flight.

Articles et Rédactions

Vidéos et Tutoriels

Manque-t-il quelque chose ?

Manquons-nous quelque chose que vous avez écrit ou enregistré ? Faites-le nous savoir avec un issue ou une pull request !

Examples

Besoin d'un démarrage rapide ?

Vous avez deux options pour démarrer un nouveau projet Flight :

Exemples contributés par la communauté :

Besoin d'inspiration ?

Bien que ces exemples ne soient pas officiellement sponsorisés par l'équipe Flight, ils pourraient vous donner des idées sur la façon de structurer vos propres projets construits avec Flight !

Voulez-vous partager votre propre exemple ?

Si vous avez un projet que vous souhaitez partager, veuillez soumettre une pull request pour l'ajouter à cette liste !

Install/install

Instructions d'installation

Il y a quelques prérequis de base avant de pouvoir installer Flight. En particulier, vous devrez :

  1. Installer PHP sur votre système
  2. Installer Composer pour une meilleure expérience de développement.

Installation de base

Si vous utilisez Composer, vous pouvez exécuter la commande suivante :

composer require flightphp/core

Cela ne placera que les fichiers principaux de Flight sur votre système. Vous devrez définir la structure du projet, l'agencement, les dépendances, les configurations, le chargement automatique, etc. Cette méthode garantit qu'aucune autre dépendance n'est installée en dehors de Flight.

Vous pouvez également télécharger les fichiers directement et les extraire dans votre répertoire web.

Installation recommandée

Il est fortement recommandé de commencer avec l'application flightphp/skeleton pour tout nouveau projet. L'installation est un jeu d'enfant.

composer create-project flightphp/skeleton my-project/

Cela configurera la structure de votre projet, configurera le chargement automatique avec des espaces de noms, mettra en place une configuration, et fournira d'autres outils comme Tracy, Extensions Tracy, et Runway

Configurer votre serveur web

Serveur de développement PHP intégré

C'est de loin la façon la plus simple de démarrer. Vous pouvez utiliser le serveur intégré pour exécuter votre application et même utiliser SQLite pour une base de données (tant que sqlite3 est installé sur votre système) sans avoir besoin de grand-chose ! Exécutez simplement la commande suivante une fois que PHP est installé :

php -S localhost:8000
# ou avec l'application skeleton
composer start

Puis ouvrez votre navigateur et allez à http://localhost:8000.

Si vous souhaitez définir le répertoire racine de documents de votre projet comme un répertoire différent (Ex : votre projet est ~/myproject, mais votre répertoire racine de documents est ~/myproject/public/), vous pouvez exécuter la commande suivante une fois dans le répertoire ~/myproject :

php -S localhost:8000 -t public/
# avec l'application skeleton, cela est déjà configuré
composer start

Puis ouvrez votre navigateur et allez à http://localhost:8000.

Apache

Assurez-vous qu'Apache est déjà installé sur votre système. Sinon, recherchez sur Google comment installer Apache sur votre système.

Pour Apache, modifiez votre fichier .htaccess avec le suivant :

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

Note : Si vous devez utiliser flight dans un sous-répertoire, ajoutez la ligne RewriteBase /subdir/ juste après RewriteEngine On.

Note : Si vous souhaitez protéger tous les fichiers du serveur, comme un fichier de base de données ou env. Mettez cela dans votre fichier .htaccess :

RewriteEngine On
RewriteRule ^(.*)$ index.php

Nginx

Assurez-vous que Nginx est déjà installé sur votre système. Sinon, recherchez sur Google comment installer Nginx sur votre système.

Pour Nginx, ajoutez le suivant à votre déclaration de serveur :

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

Créer votre fichier index.php

Si vous effectuez une installation de base, vous devrez avoir du code pour commencer.

<?php

// Si vous utilisez Composer, incluez le chargeur automatique.
require 'vendor/autoload.php';
// si vous n'utilisez pas Composer, chargez le framework directement
// require 'flight/Flight.php';

// Ensuite, définissez une route et assignez une fonction pour gérer la requête.
Flight::route('/', function () {
  echo 'hello world!';
});

// Enfin, démarrez le framework.
Flight::start();

Avec l'application skeleton, cela est déjà configuré et géré dans votre fichier app/config/routes.php. Les services sont configurés dans app/config/services.php

Installer PHP

Si vous avez déjà php installé sur votre système, passez ces instructions et allez à la section de téléchargement

macOS

Installer PHP en utilisant Homebrew

  1. Installer Homebrew (si ce n'est pas déjà installé) :

    • Ouvrez le Terminal et exécutez :
      /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  2. Installer PHP :

    • Installer la dernière version :
      brew install php
    • Pour installer une version spécifique, par exemple, PHP 8.1 :
      brew tap shivammathur/php
      brew install shivammathur/php/php@8.1
  3. Changer entre les versions de PHP :

    • Délier la version actuelle et lier la version souhaitée :
      brew unlink php
      brew link --overwrite --force php@8.1
    • Vérifier la version installée :
      php -v

Windows 10/11

Installer PHP manuellement

  1. Télécharger PHP :

    • Visitez PHP for Windows et téléchargez la dernière version ou une version spécifique (par ex., 7.4, 8.0) sous forme de fichier zip non thread-safe.
  2. Extraire PHP :

    • Extrayez le fichier zip téléchargé dans C:\php.
  3. Ajouter PHP au PATH système :

    • Allez dans Propriétés du système > Variables d'environnement.
    • Sous Variables système, trouvez Path et cliquez sur Modifier.
    • Ajoutez le chemin C:\php (ou l'endroit où vous avez extrait PHP).
    • Cliquez sur OK pour fermer toutes les fenêtres.
  4. Configurer PHP :

    • Copiez php.ini-development vers php.ini.
    • Modifiez php.ini pour configurer PHP selon vos besoins (par ex., définir extension_dir, activer les extensions).
  5. Vérifier l'installation de PHP :

    • Ouvrez l'Invite de commandes et exécutez :
      php -v

Installer plusieurs versions de PHP

  1. Répétez les étapes ci-dessus pour chaque version, en les plaçant dans un répertoire séparé (par ex., C:\php7, C:\php8).

  2. Changer entre les versions en ajustant la variable PATH système pour pointer vers le répertoire de la version souhaitée.

Ubuntu (20.04, 22.04, etc.)

Installer PHP en utilisant apt

  1. Mettre à jour les listes de paquets :

    • Ouvrez le Terminal et exécutez :
      sudo apt update
  2. Installer PHP :

    • Installer la dernière version de PHP :
      sudo apt install php
    • Pour installer une version spécifique, par exemple, PHP 8.1 :
      sudo apt install php8.1
  3. Installer des modules supplémentaires (optionnel) :

    • Par exemple, pour installer le support MySQL :
      sudo apt install php8.1-mysql
  4. Changer entre les versions de PHP :

    • Utilisez update-alternatives :
      sudo update-alternatives --set php /usr/bin/php8.1
  5. Vérifier la version installée :

    • Exécutez :
      php -v

Rocky Linux

Installer PHP en utilisant yum/dnf

  1. Activer le dépôt EPEL :

    • Ouvrez le Terminal et exécutez :
      sudo dnf install epel-release
  2. Installer le dépôt Remi :

    • Exécutez :
      sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
      sudo dnf module reset php
  3. Installer PHP :

    • Pour installer la version par défaut :
      sudo dnf install php
    • Pour installer une version spécifique, par exemple, PHP 7.4 :
      sudo dnf module install php:remi-7.4
  4. Changer entre les versions de PHP :

    • Utilisez la commande de module dnf :
      sudo dnf module reset php
      sudo dnf module enable php:remi-8.0
      sudo dnf install php
  5. Vérifier la version installée :

    • Exécutez :
      php -v

Notes générales

Guides

Guides

Flight PHP est conçu pour être simple et puissant, et nos guides vous aideront à construire des applications du monde réel étape par étape. Ces tutoriels pratiques vous guident à travers des projets complets pour démontrer comment Flight peut être utilisé efficacement.

Guides officiels

Construire un blog

Apprenez à créer une application de blog fonctionnelle avec Flight PHP. Ce guide vous guide à travers :

Ce tutoriel est parfait pour les débutants qui veulent voir comment toutes les pièces s'emboîtent dans une application réelle.

Tests unitaires et principes SOLID

Ce guide couvre les fondamentaux des tests unitaires dans les applications Flight PHP. Il inclut :

Guides non officiels

Bien que ces guides ne soient pas officiellement maintenus par l'équipe Flight, ce sont des ressources précieuses créées par la communauté. Ils couvrent divers sujets et cas d'utilisation, fournissant des insights supplémentaires sur l'utilisation de Flight PHP.

Créer une API RESTful avec Flight Framework

Ce guide vous guide à travers la création d'une API RESTful en utilisant le framework Flight PHP. Il couvre les bases de la configuration d'une API, de la définition de routes et du retour de réponses JSON.

Construire un simple blog

Ce guide vous guide à travers la création d'un blog de base en utilisant le framework Flight PHP. Il comporte en fait 2 parties : une pour couvrir les bases et l'autre pour aborder des sujets plus avancés et des raffinements pour un blog prêt pour la production.

Construire une API Pokémon en PHP : Un guide pour débutants

Ce guide amusant vous guide à travers la création d'une simple API Pokémon en utilisant Flight PHP. Il couvre les bases de la configuration d'une API, de la définition de routes et du retour de réponses JSON.

Contribuer

Vous avez une idée pour un guide ? Vous avez trouvé une erreur ? Nous accueillons les contributions ! Nos guides sont maintenus dans le dépôt de documentation FlightPHP.

Si vous avez construit quelque chose d'intéressant avec Flight et que vous souhaitez le partager en tant que guide, veuillez soumettre une pull request. Partager vos connaissances aide la communauté Flight à grandir.

À la recherche de la documentation API ?

Si vous cherchez des informations spécifiques sur les fonctionnalités et méthodes principales de Flight, consultez la section Apprendre de notre documentation.