sugatasei / bredala-http
PHP object-oriented layer for the HTTP specification
Requires
- php: >=8.5
- psr/http-factory: ^1.0
- psr/http-factory-implementation: ^1.0
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- guzzlehttp/psr7: ^3.0
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^9.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Couche d'abstraction pour PHP pour analyser des requètes HTTP et créer des réponses HTTP.
Pas de routeur, pas de conteneur d'injection, pas de middleware : uniquement un lecteur de requète, un constructeur de réponse, une couche session et une exception porteuse de statut HTTP.
Installation
composer require sugatasei/bredala-http
Le corps des réponses est un Psr\Http\Message\StreamInterface, construit via une factory PSR-17. Le package en exige une (psr/http-factory-implementation) sans en imposer une en particulier :
composer require nyholm/psr7
Response détecte automatiquement la première implémentation installée parmi nyholm/psr7, guzzlehttp/psr7, laminas/laminas-diactoros, httpsoft/http-message et slim/psr7. Si aucune n'est disponible, la première écriture dans le corps lève une LogicException. Pour choisir explicitement :
$res = Response::create()->setStreamFactory(new Nyholm\Psr7\Factory\Psr17Factory());
Claude Code
Ce package fournit un skill Claude Code dans skills/bredala-http/ qui documente les patterns d'usage et les pièges de la librairie (redirect qui reset les headers, parsing JSON automatique, sessions, etc.).
Dans un projet qui dépend de sugatasei/bredala-http, copie-le une fois dans .claude/skills/ après composer install pour que Claude Code le charge automatiquement (le nom du dossier doit correspondre au name déclaré dans SKILL.md) :
cp -r vendor/sugatasei/bredala-http/skills/bredala-http .claude/skills/bredala-http
Request
Bredala\Http\Request Récupération de données d'une requète HTTP.
Construction
create(): staticCrée une requète vide, à remplir manuellement (tests, CLI).createFromServer(): staticCrée une requète à partir des superglobales ($_SERVER,$_GET,$_COOKIE,$_FILESet le corps de la requète).
Les setters permettent de construire une requète à la main :
setUri(string $uri): staticsetServers(array $values): staticsetQueryParams(array $values): staticsetBodyParams(array $values): staticsetAttachements(array $values): staticsetCookies(array $values): static
Données du serveur
servers(): arrayRetourne un tableau des paramètres serveur ($_SERVER).server(string $name): mixedRetourne un paramètre serveur.time(): intRetourne l'horodatage en secondes de la requète.mtime(): floatRetourne l'horodatage en secondes de la requète avec une précision à la microseconde.
Méthode HTTP
method(): stringRetourne la méthode HTTP de la requète (CLI, GET, POST, DELETE, ...).isGet(): boolRetourne vrai pour une requète HTTP GET.isPost(): boolRetourne vrai pour une requète HTTP POST.isPut(): boolRetourne vrai pour une requète HTTP PUT.isPatch(): boolRetourne vrai pour une requète HTTP PATCH.isDelete(): boolRetourne vrai pour une requète HTTP DELETE.isClient(): boolRetourne vrai pour une requète depuis la ligne de commande.isAjax(): boolRetourne vrai pour une requète AJAX.isSecure(): boolRetourne vrai pour une requète HTTPS.
URL
uri(): stringRetourne l'URI demandée par la requète.baseUrl(): stringRetourne l'URL de base du site (schéma et hôte).currentUrl(bool $withQueryString = true): stringRetourne l'URL complète de la requète.
Cookies
cookies(): arrayRetourne un tableau des cookies.cookie(string $name): mixedRetourne la valeur d'un cookie.
Données de la requète
queryParams(): arrayRetourne un tableau des paramètres de la requète.queryParam(string $name): mixedRetourne la valeur d'un paramètre de la requète.bodyParams(): arrayRetourne un tableau des données du corps de la requète d'un formulaire ou JSON.bodyParam(string $name): mixedRetourne la valeur d'une donnée du corps de la requète d'un formulaire ou JSON.attachements(): arrayRetourne un tableau des données des fichiers envoyés. Indexé par le nom du champ.attachement(string $name): ?arrayRetourne les données d'un fichier envoyé.
Client
userAgent(): ?stringRetourne le user agent.ip(): stringRetourne l'adresse IP.
Response
Bredala\Http\Response Créer une réponse HTTP.
Construction
create(): staticCrée une réponse avec les valeurs par défaut.createFromServer(): staticCrée une réponse alignée sur la requète courante : version du protocole, cookies sécurisés en HTTPS et réglages CORS déduits des entêtesOriginetAccess-Control-Request-*.reset(): staticRéinitialise la réponse. La factory de flux et les réglages (cookies, CORS, buffer) sont conservés.
Configuration
setStreamFactory(StreamFactoryInterface $factory): staticDéfinit la factory PSR-17 utilisée pour construire le corps.getStreamFactory(): StreamFactoryInterfaceRetourne la factory, détectée automatiquement si aucune n'a été définie.setBuffer(int $buffer): staticDéfinit la taille du tampon utilisé à l'envoi du corps.setCookieDomain(string $domain = ''): staticsetCookiePath(string $path = '/'): staticsetCookieSecure(bool $secure = true): staticsetCookieHttponly(bool $httponly = true): staticsetCookieSamesite(string $samesite = ''): staticsetCorsOrigin(string $origin = '*'): staticsetCorsMethods(string $methods = 'GET,POST,PUT,PATCH,DELETE,OPTIONS'): staticsetCorsHeaders(string $headers = '*'): static
HTTP status
getProtocolVersion(): stringRetourne la version du protocole HTTP.setProtocolVersion(string $version): staticDéfinit la version du protocole HTTP.getStatusCode(): intRetourne le statut HTTP de la réponse.setStatusCode(int $code, ?string $reason = null): staticDéfinit le statut HTTP de la réponse. Si la raison est nulle, une valeur par défaut correspondant au code HTTP sera utilisée.
Les codes HTTP sont disponibles en constantes sur la réponse, et les libellés associés dans Response::$statusReasons (les deux viennent de Bredala\Http\HttpStatusTrait) :
$res->setStatusCode(Response::HTTP_NOT_FOUND); // 404 Not Found
HTTP headers
getHeaders(): arrayRetourne toutes les entêtes.getHeader(string $header): arrayRetourne les valeurs d'une entête.hasHeader(string $header): boolRetourne si une entête est définie.addHeader(string $name, string $value, bool $replace = false): staticAjoute une entête HTTP. Avec$replace, remplace les valeurs existantes au lieu de s'y ajouter.removeHeader(string $name): staticSupprime une entête HTTP.setContentType(string $mime, string $charset = "UTF-8"): staticDéfinit le type.$res->setContentType('jpg'); // shortcut $res->setContentType('image/jpeg'); // verbose
addCookie(string $name, $value, int $expire = 0, $settings = []): staticAjoute un cookie.$settingssurcharge ponctuellement les réglages globaux (domain,path,secure,httponly,samesite).removeCookie(string $name, $settings = []): staticSupprime un cookie. Le navigateur n'identifiant un cookie que par son nom, son domaine et son chemin,$settingsdoit reprendre ceux passés àaddCookie().redirect(string $url = "/", bool $temporary = true): staticRedirection HTTP (302 par défaut, 301 si$temporaryest faux). Réinitialise la réponse au passage : seuls les cookies déjà ajoutés sont conservés.cache(int $age = 86400): staticRaccourci pour configurer le cache HTTP.noCache(): staticDésactive le cache HTTP.cors(?string $origin = null, ?string $methods = null, ?string $headers = null): staticÉmet les entêtes CORS. Chaque argument nul retombe sur le réglage correspondant.
Http body
getBody(): StreamInterfaceRetourne le corps de la réponse.setBody(StreamInterface|resource|string|Stringable|null $body = ""): staticAjoute un contenu à la réponse. UnStreamInterfaceest utilisé tel quel ; tout autre type est refusé par uneInvalidArgumentException.setBodyFile(string $filename, string $mode = 'r'): staticAjoute un fichier comme contenu, en flux, sans le charger en mémoire.setText(string $data = ''): staticConvertit une chaîne en text/plain.setJson(mixed $data = null, int $flags = 0): staticConvertit une donnée en application/json. Encode avecJSON_THROW_ON_ERROR: une donnée non sérialisable lève uneJsonException, pas uneResponseException.setJsonException(ResponseException $ex): staticConvertit uneResponseExceptionen application/json et applique son statut à la réponse.
Rendering
emitHeaders(): voidEnvoi les entêtes au navigateur.emitBody(?int $bufferLength = null): voidEnvoi le contenu au navigateur.emit(?int $bufferLength = null): voidEnvoi entêtes et contenu au navigateur.
Session
Bredala\Http\Session Couche d'abstraction pour manipuler les sessions.
__construct(?\SessionHandlerInterface $handler = null, array $options = [])Le constructeur accepte un gestionnaire de session optionnel (memcache, db, etc) et des options de cookie :name(PHPSESSID),lifetime(7200),path(/),domain(''),secure(false),httponly(true),samesite(Lax).start(?string $id = null): staticDémarre une session, éventuellement sur un identifiant donné. À appeler à chaque requète.id(): ?stringRetourne l'identifiant de la session courante.regenerate(bool $deleteOld = true): staticRégénère l'identifiant en conservant les données. À appeler après une connexion (protection contre la fixation de session).close(): staticEcrit et ferme la session.destroy(): staticDétruit une session.reset(): staticSupprime toutes les variables sessions.all()Retourne toutes les données.has(string $name): boolRetourne si une donnée existe en session.get(string $name, mixed $default = null): mixedRécupère la valeur d'une session.set(string $name, mixed $value): staticAjoute une donnée à la session.delete(string $name): staticSupprime une donnée de la session.
Flash data
Une donnée flash existe jusqu'a la prochaine requète (a moins de la re-marquer en flash).
setFlash(string $name, mixed $value): staticmarkFlash(string $name): staticunmarkFlash(string $name)
Temp data
Une donnée temporaire existe une certaine durée.
setTemp(string $name, mixed $value, int $time = 300)markTemp(string $name, int $time = 300)unmarkTemp(string $name)
ResponseException
Bredala\Http\ResponseException Utilisée pour gérer globalement les erreurs des réponses HTTP.
Hérite de Exception et implémente JsonSerializable. Une seule exception couvre tous les statuts : le code HTTP est passé au constructeur et se relit via getCode().
public function __construct(int $status, string $message = 'default', ?Throwable $previous = null)
throw new ResponseException(Response::HTTP_NOT_FOUND, 'Utilisateur introuvable');
Données additionnelles
setErrors(array $errors): staticConfigure les erreurs.addError(string $key, mixed $value): staticAjoute une erreur.getErrors(): arrayRetourne les erreurs.setExtra(array $extra): staticConfigure des données additionnelles.addExtra(string $key, mixed $value): staticAjoute une donnée additionnelle.getExtra(): arrayRetourne les données additionnelles.jsonSerialize(): mixedRetourne la représentation JSON de l'exception.
La sérialisation produit toujours les quatre mêmes clés :
{
"status": 422,
"error": "Données invalides",
"errors": {"email": "Format invalide"},
"extra": []
}
errors et extra sont toujours sérialisés en tableau [] lorsqu'ils sont vides, jamais en objet {}. Ils deviennent un objet JSON dès qu'une clé y est ajoutée.
Statuts courants
Les constantes de Response évitent d'écrire les codes à la main.
| Constante | Code | Usage |
|---|---|---|
Response::HTTP_BAD_REQUEST |
400 | Erreur provenant de l'utilisateur, erreur de validation. |
Response::HTTP_UNAUTHORIZED |
401 | Informations d'authentification non valides. |
Response::HTTP_FORBIDDEN |
403 | Accès interdit. |
Response::HTTP_NOT_FOUND |
404 | Ressource introuvable. |
Response::HTTP_NOT_ACCEPTABLE |
406 | Format de réponse non négociable. |
Response::HTTP_GONE |
410 | Ressource expirée. |
Response::HTTP_UNPROCESSABLE_ENTITY |
422 | Entité bien formée mais sémantiquement invalide. |
Response::HTTP_LOCKED |
423 | Ressource verrouillée. |
Response::HTTP_INTERNAL_SERVER_ERROR |
500 | Erreur d'exécution, telle une panne. |
Utilisation
Lever l'exception depuis la logique métier, la rattraper une fois à la frontière :
use Bredala\Http\Request; use Bredala\Http\Response; use Bredala\Http\ResponseException; $req = Request::createFromServer(); $res = Response::createFromServer(); try { $user = $repository->find($req->queryParam('id')) ?? throw new ResponseException(Response::HTTP_NOT_FOUND, 'Utilisateur introuvable'); $res->setJson($user); } catch (ResponseException $ex) { $res->setJsonException($ex); } $res->emit();