sopheos / pebble_s3
Thin S3 object storage layer over the AWS SDK for PHP
Requires
- php: >=8.5.0
- ext-fileinfo: *
- aws/aws-sdk-php: ^3.395
- psr/http-message: ^2.0
Requires (Dev)
- phpunit/phpunit: ^9.5
Suggests
- ext-imagick: Permet de stocker directement des images Imagick via Store::set()
Provides
None
Conflicts
None
Replaces
None
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 parsetFile()) ext-imagick, facultative : uniquement pour passer une imageImagickà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) : staticClient AWS à utiliser.client() : S3ClientClient courant. LèveRuntimeException('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 uneError« call on null » (voirTODO.md).setBucket(string $bucket) : static/getBucket() : stringBucket courant.getBucket()lèveRuntimeException('bucket_undefined')s'il n'est pas défini.createBucket() : boolCrée le bucket s'il n'existe pas (HeadBucketpuisCreateBucketsur 404). Renvoietrues'il a été créé.has(string $key) : boolExistence d'un objet (HeadObject). Un 403 est relancé.get(string $key) : ?ItemLit un objet, ounullsur 404.set(string $key, mixed $data, string $contentType = Store::DEFAULT_TYPE) : stringEnvoie une chaîne, une ressource, unStreamInterfaceou une imageImagick. Renvoie la clé.setFile(string $key, string $path, string $contentType = Store::DEFAULT_TYPE) : ?stringEnvoie un fichier local. Le type détecté parmime_content_type()l'emporte sur$contentType(voirTODO.md). Renvoie la clé.delete(string $key) : boolSupprime un objet. Renvoie toujourstrue.deleteMany(array $keys) : arraySupprime par lots de 1000 et renvoie les clés en échec.list(string $prefix = '') : iterableGé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) : staticConstruit l'item depuis un résultatGetObject. LeBodyest obligatoire.$bodyStream PSR-7 brut.$type(application/octet-streampar défaut),$length(octets),$date(timestamp Unix,0si inconnu),$etag(sans guillemets, ounull).body(bool $rewind = true) : StreamInterfaceLe stream, rembobiné s'il est seekable.createResource() : resourceRembobine puis détache la ressource PHP native. L'item n'est plus lisible ensuite, un second appel lèveRuntimeException('not_resource').
IterableHelper
IterableHelper::chunk(iterable $items, int $size) : iterableGénérateur de lots de$sizeéléments, le dernier pouvant être plus petit. Les clés d'origine sont perdues. Une taille<= 0produit 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.