Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: Bump php74 + improve api #37

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
max-parallel: 1
fail-fast: false
matrix:
php: ['7.2', '7.3', '7.4', '8.0']
php: ['7.4', '8.0']
name: Tests - PHP ${{ matrix.php }}
steps:
- name: Checkout code
Expand Down
20 changes: 13 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@
{
"name": "Roberto Moreno",
"email": "rmorenp@rampmaster.org",
"homepage": "http://www.rampmaster.org",
"homepage": "https://rampmaster.org/",
"role": "Wrapper Developer"
},
{
"name": "Colin Benoit",
"homepage": "https://github.com/Benoit382",
"role": "PHP Developer"
}
],
"require": {
"php": "^7.2|^8.0",
"php": "^7.4|^8.0",
"ext-json": "*",
"ext-curl": "*",
"guzzlehttp/guzzle": "^6.3|^7.0",
"guzzlehttp/guzzle": "^7.4",
"psr/log": "^1.0",
"psr/simple-cache": "^1.0"
},
Expand All @@ -36,11 +41,12 @@
},
"require-dev": {
"ext-gd": "*",
"monolog/monolog": "^1.23",
"symfony/cache": "^4.3",
"phpunit/phpunit": "^8.5|^9.5",
"monolog/monolog": "^2.3",
"symfony/cache": "^5.3",
"phpunit/phpunit": "^9.5",
"phpstan/phpstan": "^0.12.99",
"phpstan/phpstan-phpunit": "^0.12.22",
"friendsofphp/php-cs-fixer": "^3.1"
"friendsofphp/php-cs-fixer": "^3.2",
"symfony/var-dumper": "^5.3"
}
}
9 changes: 7 additions & 2 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@
<server name="KERNEL_CLASS" value="AppKernel"/>
</php>
<testsuites>
<testsuite name="AllTests">
<directory>tests</directory>
<testsuite name="Functional">
<directory>tests/Functional</directory>
</testsuite>

<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>

</testsuites>
<logging>
<junit outputFile="./build/logs/junit.xml"/>
Expand Down
178 changes: 72 additions & 106 deletions src/Api.php
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
<?php

/** @noinspection ALL */
declare(strict_types=1);

namespace OpenFoodFacts;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\TransferStats;
use OpenFoodFacts\Api\ApiClient;
use OpenFoodFacts\Api\CgiClient;
use OpenFoodFacts\Api\DownloadClient;
use OpenFoodFacts\Exception\BadRequestException;
use OpenFoodFacts\Exception\ProductNotFoundException;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Psr\SimpleCache\CacheInterface;
use Psr\SimpleCache\InvalidArgumentException;

/**
* this class provide [...]
*
* It a fork of the python OpenFoodFact rewrite on PHP 7.2
* @method getIngredients() Collection
Expand All @@ -26,23 +27,24 @@
*/
class Api
{
use ApiClient;
use DownloadClient;
use CgiClient;

/**
* the httpClient for all http request
* @var ClientInterface
*/
private $httpClient;
private ClientInterface $httpClient;

/**
* this property store the current base of the url
* @var string
*/
private $geoUrl = 'https://%s.openfoodfacts.org';
private string $host = 'https://%s.openfoodfacts.org';

/**
* this property store the current API (it could be : food/beauty/pet )
* @var string
*/
private $currentAPI = '';
private string $currentAPI ;

/**
* This property store the current location for http call
Expand All @@ -53,27 +55,20 @@ class Api
*
* @example fr-en
* @link https://en.wiki.openfoodfacts.org/API/Read#Country_code_.28cc.29_and_Language_of_the_interface_.28lc.29
* @var string
*/
private $geography = 'world';
private string $geography;

/**
* this property store the auth parameter (username and password)
* @var array
*/
private $auth = null;
private array $auth = [];

/**
* this property help you to log information
* @var LoggerInterface
*/
private $logger = null;
private LoggerInterface $logger;


/**
* @var CacheInterface|null
*/
private $cache;
private ?CacheInterface $cache;

/**
* this constant defines the environments usable by the API
Expand All @@ -84,6 +79,7 @@ class Api
'beauty' => 'https://%s.openbeautyfacts.org',
'pet' => 'https://%s.openpetfoodfacts.org',
'product' => 'https://%s.openproductsfacts.org',
'test' => 'https://world.openfoodfacts.net',
];

/**
Expand Down Expand Up @@ -115,16 +111,6 @@ class Api
'traces',
];

/**
* This constant defines the extensions authorized for the downloading of the data
* @var array
*/
private const FILE_TYPE_MAP = [
"mongodb" => "openfoodfacts-mongodbdump.tar.gz",
"csv" => "en.openfoodfacts.org.products.csv",
"rdf" => "en.openfoodfacts.org.products.rdf"
];

/**
* the constructor of the function
*
Expand All @@ -138,39 +124,62 @@ public function __construct(
string $api = 'food',
string $geography = 'world',
LoggerInterface $logger = null,
ClientInterface $clientInterface = null,
ClientInterface $httpClient = null,
CacheInterface $cacheInterface = null
) {
$this->cache = $cacheInterface;
$this->logger = $logger ?? new NullLogger();
$this->httpClient = $clientInterface ?? new Client();
$this->httpClient = $httpClient ?? new Client();

$this->geoUrl = sprintf(self::LIST_API[$api], $geography);
$this->geography = $geography;
$this->host = sprintf(self::LIST_API[$api], $geography);
$this->geography = $geography;
$this->currentAPI = $api;
if ($api === 'test') {
$this->geography = 'world';
$this->currentAPI = 'food';
$this->setAuthentication('off', 'off');
}
}

/**
* This function allows you to perform tests
* The domain is correct and for testing purposes only
*/
public function activeTestMode(): void
protected function getHttpClient(): ClientInterface
{
return $this->httpClient;
}

protected function requestJson(string $method, $uri, array $options = []): array
{
try {
$response = $this->httpClient->request($method, $uri, $options);
} catch (GuzzleException $guzzleException) {
$this->logger->warning(sprintf('OpenFoodFact - fetch - failed - %s : %s', $method, $uri), ['exception' => $guzzleException]);

throw new BadRequestException($guzzleException->getMessage(), $guzzleException->getCode(), $guzzleException);
}

return json_decode($response->getBody()->getContents(), true);
}

protected function getHost(): string
{
$this->geoUrl = 'https://world.openfoodfacts.net';
$this->authentification('off', 'off');
return $this->host;
}

public function getCurrentApi(): string
protected function getCurrentApi(): string
{
return $this->currentAPI;
}

protected function getAuthentication(): array
{
return $this->auth;
}

/**
* This function store the authentication parameter
* @param string $username
* @param string $password
*/
public function authentification(string $username, string $password): void
public function setAuthentication(string $username, string $password): void
{
$this->auth = [
'user_id' => $username,
Expand Down Expand Up @@ -224,27 +233,6 @@ public function __call(string $name, $arguments): Collection
}


/**
* this function search an Document by barcode
* @param string $barcode the barcode [\d]{13}
* @return Document A Document if found
* @throws InvalidArgumentException
* @throws ProductNotFoundException
* @throws BadRequestException
*/
public function getProduct(string $barcode): Document
{
$url = $this->buildUrl('api', 'product', $barcode);

$rawResult = $this->fetch($url);
if ($rawResult['status'] === 0) {
//TODO: maybe return null here? (just throw an exception if something really went wrong?
throw new ProductNotFoundException("Product not found", 1);
}

return Document::createSpecificDocument($this->currentAPI, $rawResult['product']);
}

/**
* This function return a Collection of Document search by facets
* @param array $query list of facets with value
Expand Down Expand Up @@ -301,7 +289,7 @@ public function addNewProduct(array $postData)
* @throws BadRequestException
* @throws InvalidArgumentException
*/
public function uploadImage(string $code, string $imageField, string $imagePath)
public function uploadImage(string $code, string $imageField, string $imagePath): array
{
//TODO : need test
if ($this->currentAPI !== 'food') {
Expand Down Expand Up @@ -334,7 +322,7 @@ public function uploadImage(string $code, string $imageField, string $imagePath)
* @throws BadRequestException
* @throws InvalidArgumentException
*/
public function search(string $search, int $page = 1, int $pageSize = 20, string $sortBy = 'unique_scans')
public function search(string $search, int $page = 1, int $pageSize = 20, string $sortBy = 'unique_scans'): Collection
{
$parameters = [
'search_terms' => $search,
Expand All @@ -356,31 +344,16 @@ public function search(string $search, int $page = 1, int $pageSize = 20, string
* @return bool return true when download is complete
* @throws BadRequestException
*/
public function downloadData(string $filePath, string $fileType = "mongodb")
public function downloadData(string $filePath, string $fileType = "mongodb"): bool
{
if (!isset(self::FILE_TYPE_MAP[$fileType])) {
$this->logger->warning(
'OpenFoodFact - fetch - failed - File type not recognized!',
['fileType' => $fileType, 'availableTypes' => self::FILE_TYPE_MAP]
);
throw new BadRequestException('File type not recognized!');
}

$url = $this->buildUrl('data', self::FILE_TYPE_MAP[$fileType]);
try {
$response = $this->httpClient->request('get', $url, ['sink' => $filePath]);
} catch (GuzzleException $guzzleException) {
$this->logger->warning(sprintf('OpenFoodFact - fetch - failed - GET : %s', $url), ['exception' => $guzzleException]);
$exception = new BadRequestException($guzzleException->getMessage(), $guzzleException->getCode(), $guzzleException);
return $this->download($filePath, $fileType);
} catch (BadRequestException $e) {
//@todo: discus on PR => warning or error ?
$this->logger->warning('OpenFoodFact - '.$e->getMessage(), ['exception' => $e]);

throw $exception;
throw $e;
}

$this->logger->info('OpenFoodFact - fetch - GET : ' . $url . ' - ' . $response->getStatusCode());

//TODO: validate response here (server may respond with 200 - OK but you might not get valid data as a response)

return $response->getStatusCode() == 200;
}


Expand Down Expand Up @@ -428,7 +401,7 @@ private function fetch(string $url, bool $isJsonFile = true): array
}
$this->logger->info('OpenFoodFact - fetch - GET : ' . $url . ' - ' . $response->getStatusCode());

$jsonResult = json_decode($response->getBody(), true);
$jsonResult = json_decode($response->getBody()->getContents(), true);

if (!empty($this->cache) && !empty($jsonResult)) {
$this->cache->set($cacheKey, $jsonResult);
Expand Down Expand Up @@ -501,25 +474,18 @@ private function buildUrl(string $service = null, $resourceType = null, $paramet
switch ($service) {
case 'api':
$baseUrl = implode('/', [
$this->geoUrl,
$service,
'v0',
$resourceType,
$parameters
]);
break;
case 'data':
$baseUrl = implode('/', [
$this->geoUrl,
$service,
$resourceType
$this->host,
$service,
'v0',
$resourceType,
$parameters
]);
break;
case 'cgi':
$baseUrl = implode('/', [
$this->geoUrl,
$service,
$resourceType
$this->host,
$service,
$resourceType
]);
$baseUrl .= '?' . (is_array($parameters) ? http_build_query($parameters) : $parameters);
break;
Expand All @@ -534,9 +500,9 @@ private function buildUrl(string $service = null, $resourceType = null, $paramet
$parameters = 1;
}
$baseUrl = implode('/', array_filter([
$this->geoUrl,
$resourceType,
$parameters
$this->host,
$resourceType,
$parameters
], function ($value) {
return !empty($value);
}));
Expand Down
Loading