🇫🇷 Version française · 🇬🇧 English version
Fork de openfoodfacts/openfoodfacts-php, le wrapper PHP pour Open Food Facts, la base de données ouverte sur les produits alimentaires.
Ce fork migre la lecture, l’écriture et l’upload d’images produit vers l'API Open Food Facts v3.6 (schéma produit 1004) et corrige plusieurs bugs de robustesse de la version upstream (v0.4.0), restée sur l'API v0 legacy.
📖 Documentation complète : doc/home.md — ce README se concentre sur les différences avec l'upstream.
🚀 Démo en ligne : openfoodfacts-php-demo.fly.dev — interface de test du wrapper : lecture complète d'un produit, recherche, changement de langue, et la réponse JSON brute renvoyée par le SDK. Édition et upload de photos disponibles sur le serveur de staging avec un compte contributeur. Interface en français et en anglais. Code source complet : examples/02_demo.
La migration v3 concerne les produits et leurs images. Api::search() utilise encore cgi/search.pl ; getByFacets() et les accesseurs de facettes utilisent les URL .json du site. Il n’existe pas de recherche v3 : voir la matrice officielle des API. Le client SearchApi expose séparément Search-a-licious.
Le fork n'est pas publié sur Packagist : installez-le via un dépôt VCS dans votre composer.json :
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/TaciteOFF/openfoodfacts-php" }
],
"require": {
"taciteoff/openfoodfacts-php": "^1.1"
}
}Le nom Composer du fork est taciteoff/openfoodfacts-php ; les namespaces PHP restent OpenFoodFacts\. Retirez l’ancienne dépendance openfoodfacts/openfoodfacts-php avant migration : les deux packages ne peuvent pas cohabiter. Le nouveau nom est disponible à partir de la version 1.1.0 ; les tags antérieurs gardent l’ancien nom.
// Le user agent (1er argument) est obligatoire — décrivez votre application
$api = new OpenFoodFacts\Api('MonApp - Web - 1.0 - https://example.org', 'food', 'fr');
// Lecture via l'API v3.6 : filtrez les champs (recommandé) et localisez la réponse
$product = $api->getProduct('3057640385148', ['product_name', 'nutriscore_grade'], 'fr');
echo $product->product_name;
// Écriture structurée (PATCH v3) et upload d'image (POST v3) — credentials requis
$api->authentification('utilisateur', 'mot-de-passe');
$api->updateProduct('3057640385148', ['product_name_fr' => 'Eau de Volvic', 'categories_tags' => ['en:waters']]);
$api->uploadImage('3057640385148', 'front', '/chemin/image.jpg', 'fr');Réf. : changelog API et schéma produit
| Fonction | Upstream (v0.4.0) | Ce fork |
|---|---|---|
| Lecture produit | GET /api/v0/product/{code} |
GET /api/v3.6/product/{code} (schéma produit 1004 épinglé) |
| Paramètres de lecture | aucun | fields, lc, cc, tags_lc (optionnels, rétrocompatibles) |
| Écriture produit | cgi/product_jqm2.pl (formulaire legacy) |
nouvelle méthode updateProduct() : PATCH /api/v3.6/product/{code} en JSON structuré (champs par langue, tags, packagings, sélection d'images) |
| Upload d'image | cgi/product_image_upload.pl (multipart, food uniquement) |
POST /api/v3.6/product/{code}/images (base64 + structure selected par champ/langue, tous les flavors) |
| Enveloppe de réponse | status 0/1 |
enveloppe v3 (status, result, errors[], warnings[]) avec messages d'erreur lisibles extraits de errors[] |
| Écriture partiellement rejetée | non détectable | success_with_errors → ProductUpdateException (l'enveloppe complète reste accessible via getResponse()) ; success_with_warnings journalisé via le logger |
| Produit d'un autre type (cross-flavor) | non géré | paramètre product_type de getProduct() (ex. 'all') : le serveur redirige vers le bon flavor, redirection suivie automatiquement |
| Redirections | strict = false (un PATCH/POST redirigé en 301/302 serait rétrogradé en GET sans corps) |
mode strict : la méthode et le corps sont préservés sur les redirections |
Détails d'implémentation :
Api::API_VERSION = '3.6'(constante publique) : toutes les routes produit sont versionnées, la structure de réponse reste stable même quand le serveur évolue.addNewProduct()(cgi legacy) est conservée mais dépréciée au profit deupdateProduct().Document::__getrenvoienullpour un champ absent (au lieu d'un warning PHP) : indispensable avec la v3.6, qui supprime les champs*_hierarchyet*_lcau profit detags_sources, et depuis la v3.1 renommeecoscore_*enenvironmental_score_*.
- Les réponses POST ne sont plus mises en cache : dans l'upstream, un second envoi identique (
addNewProduct,uploadImage) dans la durée de vie du cache ne faisait jamais la requête — une écriture silencieusement perdue. La clé de cache entrait de plus en collision pour tous les uploads (ressource non sérialisable parjson_encode). - TTL sur le cache de lecture (1 h) : l'upstream cachait les fiches produit sans expiration.
- Décodage JSON strict (
JSON_THROW_ON_ERROR) et vérification du statut HTTP : une page HTML d'erreur du serveur provoquait unTypeErrordans l'upstream. - Validation du code-barres (chiffres uniquement) et encodage URL : plus d'injection possible de segments d'URL via
getProduct(). 404→ProductNotFoundException, credentials manquants →MissingCredentialsException, code-barres invalide →InvalidBarcodeException(étendInvalidParameterException, valeur rejetée accessible viagetBarcode()).activeTestMode()distingue le htaccess du staging des identifiants du contributeur :off/offest la protection htaccess (HTTP Basic) du serveur de test.net, pas un compte contributeur. Le SDK l'envoie dans l'en-têteAuthorization, tandis que les identifiants du contributeur — fournis viaauthentification()— partent dans le corps des écritures v3 (user_id/password). L'upstream confondait les deux :activeTestMode()écrasait les identifiants du contributeur avecoff/off, faisant échouer toute écriture sur le staging.
- Les exemples du README upstream ne compilaient plus (le constructeur exige
$userAgenten premier argument depuis la 0.4.0) ;cached_example.phppassait en plus unintàgetProduct(string). - Le badge Travis CI mort a été retiré.
- Nouvelle suite unitaire
tests/Unit/OpenFoodFacts/ApiV3Test.php(surMockHandlerGuzzle) : URL versionnée, enveloppe v3, 404, erreurs lisibles, corps PATCH, payload base64 — sans dépendre de l'API live. - Tests d'intégration mis en cohérence (la restriction « upload food uniquement » n'existe plus en v3).
- PHP 8.1 → 8.5 (aucune syntaxe au-delà de 8.1 ; testé notamment sous 8.3).
- Dépendances inchangées : Guzzle 7, PSR-3, PSR-16.
- API publique rétrocompatible, avec les changements suivants :
uploadImage()limite les fichiers à 10 Mio avant encodage, retourne désormais l'enveloppe v3 et fonctionne pour tous les flavors ; les codes-barres non numériques sont rejetés ;activeTestMode()ne pose plusoff/offque comme htaccess du.net— pour écrire sur le staging, fournissez un compte contributeur viaauthentification(). - Les nouvelles exceptions (
InvalidParameterException,UnknownException,ProductUpdateException) étendentBadRequestException: uncatch (BadRequestException)écrit contre l'upstream continue de les attraper.
Code sous licence MIT, comme l'upstream. Les données Open Food Facts sont sous licence OdBL : mentionnez la source et contribuez en retour les produits que vous ajoutez. Merci aux auteurs du wrapper original (liste) et à la communauté Open Food Facts.