Search by

sopheos / pebble_s3

sopheos

Thin S3 object storage layer over the AWS SDK for PHP

Package info

github.com/sopheos/pebble_s3

pkg:composer/sopheos/pebble_s3

Statistics

Installs: 31

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.1.5 2026-10-06 12:45 UTC

This package is auto-updated.

Last update: 2026-10-30 14:57:49 UTC


README

Une petite couche PHP au-dessus du SDK AWS pour stocker et récupérer des objets compatibles S3.

Elle fournit une API simple pour créer un bucket, vérifier l'existence d'un objet, envoyer des chaînes, ressources ou fichiers, récupérer leurs métadonnées, les supprimer et les lister.

Prérequis

  • PHP 8.5 ou supérieur, avec ext-fileinfo (détection du type MIME par setFile())
  • ext-imagick, facultative : uniquement pour passer une image Imagick à set()
  • Un endpoint S3 et des identifiants AWS valides

Installation

composer require sopheos/pebble_s3

Le paquet installe automatiquement aws/aws-sdk-php.

Claude Code

Ce package fournit un skill Claude Code dans skills/pebble-s3/. Il documente les patterns d'usage et les pièges de la librairie : client injecté et non construit, tout 404 absorbé (y compris un bucket absent), list() paresseux, setFile() qui ignore le type MIME passé, createResource() qui détache le flux, etc.

Dans un projet qui dépend de sopheos/pebble_s3, 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/sopheos/pebble_s3/skills/pebble-s3 .claude/skills/pebble-s3

Pour la maintenance de la lib elle-même, voir CLAUDE.md. Les bugs connus sont listés dans TODO.md.

Configuration

Créez un client du SDK AWS avec les paramètres adaptés à votre fournisseur S3 :

use Aws\S3\S3Client;
use Pebble\S3\Store;

$client = new S3Client([
    'version' => 'latest',
    'region' => 'eu-west-3',
    'credentials' => [
        'key' => $_ENV['AWS_ACCESS_KEY_ID'],
        'secret' => $_ENV['AWS_SECRET_ACCESS_KEY'],
    ],
]);

$store = (new Store())
    ->setClient($client)
    ->setBucket('mon-bucket');

Pour un endpoint S3 compatible (MinIO, Scaleway, etc.), ajoutez notamment endpoint et, selon le fournisseur, use_path_style_endpoint à la configuration du client AWS.

Store

\Pebble\S3\Store ne construit pas de client : il faut lui injecter un S3Client et un nom de bucket.

  • setClient(S3Client $client) : static Client AWS à utiliser.
  • client() : S3Client Client courant. Lève RuntimeException('client_undefined') s'il n'est pas défini. Attention : les autres méthodes ne passent pas par cet accesseur, un client manquant y provoque une Error « call on null » (voir TODO.md).
  • setBucket(string $bucket) : static / getBucket() : string Bucket courant. getBucket() lève RuntimeException('bucket_undefined') s'il n'est pas défini.
  • createBucket() : bool Crée le bucket s'il n'existe pas (HeadBucket puis CreateBucket sur 404). Renvoie true s'il a été créé.
  • has(string $key) : bool Existence d'un objet (HeadObject). Un 403 est relancé.
  • get(string $key) : ?Item Lit un objet, ou null sur 404.
  • set(string $key, mixed $data, string $contentType = Store::DEFAULT_TYPE) : string Envoie une chaîne, une ressource, un StreamInterface ou une image Imagick. Renvoie la clé.
  • setFile(string $key, string $path, string $contentType = Store::DEFAULT_TYPE) : ?string Envoie un fichier local. Le type détecté par mime_content_type() l'emporte sur $contentType (voir TODO.md). Renvoie la clé.
  • delete(string $key) : bool Supprime un objet. Renvoie toujours true.
  • deleteMany(array $keys) : array Supprime par lots de 1000 et renvoie les clés en échec.
  • list(string $prefix = '') : iterable Générateur paresseux sur toutes les clés, pagination comprise.

Store::DEFAULT_TYPE vaut application/octet-stream.

Item

\Pebble\S3\Item représente un objet lu par get().

  • fromResult(Result $result) : static Construit l'item depuis un résultat GetObject. Le Body est obligatoire.
  • $body Stream PSR-7 brut. $type (application/octet-stream par défaut), $length (octets), $date (timestamp Unix, 0 si inconnu), $etag (sans guillemets, ou null).
  • body(bool $rewind = true) : StreamInterface Le stream, rembobiné s'il est seekable.
  • createResource() : resource Rembobine puis détache la ressource PHP native. L'item n'est plus lisible ensuite, un second appel lève RuntimeException('not_resource').

IterableHelper

  • IterableHelper::chunk(iterable $items, int $size) : iterable Générateur de lots de $size éléments, le dernier pouvant être plus petit. Les clés d'origine sont perdues. Une taille <= 0 produit un seul lot avec tout l'itérable.

Exemples

Buckets et existence

// Crée le bucket s'il n'existe pas. Retourne true s'il a été créé.
$created = $store->createBucket();

// Vérifie la présence d'un objet.
$exists = $store->has('documents/rapport.pdf');

Envoyer un objet

// Envoyer le contenu d'une chaîne ou d'une ressource PHP.
$store->set('notes/bonjour.txt', 'Bonjour !', 'text/plain');

// Envoyer un fichier local ; son type MIME est détecté automatiquement
// (le troisième argument n'est utilisé que si la détection échoue).
$store->setFile('images/logo.png', '/chemin/vers/logo.png');

set() accepte une chaîne, une ressource, un stream PSR-7 (StreamInterface) ou une instance Imagick ; dans ce dernier cas, le type MIME de l'image est utilisé s'il est connu. setFile() exige un chemin vers un fichier existant.

Lire un objet

$item = $store->get('notes/bonjour.txt');

if ($item !== null) {
    $stream = $item->body();       // Stream PSR-7, rembobiné par défaut
    $type = $item->type;           // par exemple : text/plain
    $length = $item->length;       // taille en octets
    $timestamp = $item->date;      // date de modification Unix
    $etag = $item->etag;
}

get() retourne null si la clé est absente. Pour obtenir une ressource PHP native, utilisez $item->createResource() ; cette opération détache le flux PSR-7.

Supprimer et lister

// La suppression d'une clé absente est considérée comme réussie.
$store->delete('notes/bonjour.txt');

// Renvoie les clés en erreur ; les suppressions sont regroupées par lots de 1 000.
$failedKeys = $store->deleteMany(['a.txt', 'b.txt']);

// Itère sur toutes les clés, éventuellement sous un préfixe.
foreach ($store->list('images/') as $key) {
    echo $key, PHP_EOL;
}

Parcourir par lots

IterableHelper::chunk() découpe n'importe quel itérable (tableau ou générateur) en lots de taille fixe, sans le charger entièrement en mémoire. Le dernier lot peut être plus petit.

C'est pratique pour traiter le résultat de list(), qui est un générateur paginé :

use Pebble\S3\IterableHelper;

// Supprimer tous les objets sous un préfixe, 1 000 clés à la fois.
foreach (IterableHelper::chunk($store->list('tmp/'), 1000) as $keys) {
    $failedKeys = $store->deleteMany($keys);
}

Les clés de l'itérable d'origine ne sont pas conservées : chaque lot est une liste indexée à partir de 0.

Gestion des erreurs

Les erreurs renvoyées par S3 sont propagées sous forme de Aws\Exception\AwsException, à l'exception des 404 traités par createBucket(), has(), get() et delete(). Tout 404 est absorbé, y compris un bucket inexistant (NoSuchBucket) : has() renvoie alors false et get() null. Les entrées invalides des méthodes d'écriture génèrent une InvalidArgumentException (invalid_file_path, unsupported_file).

list() est un générateur : aucune requête n'est faite, et aucune erreur n'est levée, avant la première itération.

Tests

composer install
vendor/bin/phpunit

Aucun appel réseau : les tests utilisent un S3Client construit avec Aws\MockHandler. Les bugs connus sont figés par des tests annotés // BUG: qui vérifient le comportement actuel.

Licence

Distribué sous licence MIT.