From 1f6278525ae3f963af415efd9cb5f9e48ac1f323 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:50:48 +0100 Subject: [PATCH 01/17] :sparkles: add support for new V2 products --- src/Input/InferenceParameters.php | 38 +++--------- src/Input/LocalResponse.php | 2 +- .../{CommonResponse.php => BaseResponse.php} | 8 +-- src/Parsing/V2/Inference.php | 51 +++++----------- src/Parsing/V2/InferenceResponse.php | 10 ++-- src/Parsing/V2/JobResponse.php | 10 ++-- src/V2/ClientOptions/BaseParameters.php | 60 +++++++++++++++++++ src/V2/Parsing/BaseInference.php | 55 +++++++++++++++++ .../ClassificationClassifier.php | 30 ++++++++++ .../ClassificationInference.php | 39 ++++++++++++ .../Classification/ClassificationResponse.php | 25 ++++++++ .../Classification/ClassificationResult.php | 30 ++++++++++ .../Params/ClassificationParameters.php | 32 ++++++++++ src/V2/Product/Crop/CropInference.php | 33 ++++++++++ src/V2/Product/Crop/CropItem.php | 38 ++++++++++++ src/V2/Product/Crop/CropResponse.php | 25 ++++++++ src/V2/Product/Crop/CropResult.php | 30 ++++++++++ src/V2/Product/Crop/Params/CropParameters.php | 32 ++++++++++ src/V2/Product/Ocr/OcrInference.php | 33 ++++++++++ src/V2/Product/Ocr/OcrPage.php | 42 +++++++++++++ src/V2/Product/Ocr/OcrResponse.php | 25 ++++++++ src/V2/Product/Ocr/OcrResult.php | 41 +++++++++++++ src/V2/Product/Ocr/OcrWord.php | 38 ++++++++++++ src/V2/Product/Ocr/Params/OcrParams.php | 32 ++++++++++ .../Product/Split/Params/SplitParameters.php | 32 ++++++++++ src/V2/Product/Split/SplitInference.php | 33 ++++++++++ src/V2/Product/Split/SplitRange.php | 39 ++++++++++++ src/V2/Product/Split/SplitResponse.php | 25 ++++++++ src/V2/Product/Split/SplitResult.php | 32 ++++++++++ 29 files changed, 839 insertions(+), 81 deletions(-) rename src/Parsing/V2/{CommonResponse.php => BaseResponse.php} (68%) create mode 100644 src/V2/ClientOptions/BaseParameters.php create mode 100644 src/V2/Parsing/BaseInference.php create mode 100644 src/V2/Product/Classification/ClassificationClassifier.php create mode 100644 src/V2/Product/Classification/ClassificationInference.php create mode 100644 src/V2/Product/Classification/ClassificationResponse.php create mode 100644 src/V2/Product/Classification/ClassificationResult.php create mode 100644 src/V2/Product/Classification/Params/ClassificationParameters.php create mode 100644 src/V2/Product/Crop/CropInference.php create mode 100644 src/V2/Product/Crop/CropItem.php create mode 100644 src/V2/Product/Crop/CropResponse.php create mode 100644 src/V2/Product/Crop/CropResult.php create mode 100644 src/V2/Product/Crop/Params/CropParameters.php create mode 100644 src/V2/Product/Ocr/OcrInference.php create mode 100644 src/V2/Product/Ocr/OcrPage.php create mode 100644 src/V2/Product/Ocr/OcrResponse.php create mode 100644 src/V2/Product/Ocr/OcrResult.php create mode 100644 src/V2/Product/Ocr/OcrWord.php create mode 100644 src/V2/Product/Ocr/Params/OcrParams.php create mode 100644 src/V2/Product/Split/Params/SplitParameters.php create mode 100644 src/V2/Product/Split/SplitInference.php create mode 100644 src/V2/Product/Split/SplitRange.php create mode 100644 src/V2/Product/Split/SplitResponse.php create mode 100644 src/V2/Product/Split/SplitResult.php diff --git a/src/Input/InferenceParameters.php b/src/Input/InferenceParameters.php index 762e63f7..f1e79076 100644 --- a/src/Input/InferenceParameters.php +++ b/src/Input/InferenceParameters.php @@ -2,16 +2,13 @@ namespace Mindee\Input; +use Mindee\V2\ClientOptions\BaseParameters; + /** * Parameters accepted by the asynchronous **inference** v2 endpoint. */ -class InferenceParameters +class InferenceParameters extends BaseParameters { - /** - * @var string Model ID. - */ - public string $modelId; - /** * @var boolean|null Enhance extraction accuracy with Retrieval-Augmented Generation.. */ @@ -33,15 +30,6 @@ class InferenceParameters */ public ?bool $confidence; - /** - * @var string|null Optional file alias. - */ - public ?string $alias; - - /** - * @var array Optional webhook IDs. - */ - public array $webhooksIds; /** * @var string|null Additional text context used by the model during inference. @@ -55,9 +43,9 @@ class InferenceParameters public ?DataSchema $dataSchema; /** - * @var PollingOptions Polling options. + * @var string Slug of the endpoint. */ - public PollingOptions $pollingOptions; + public static string $slug = "extraction"; /** * @param string $modelId ID of the model. @@ -87,27 +75,15 @@ public function __construct( DataSchema|string|array|null $dataSchema = null, ?PollingOptions $pollingOptions = null, ) { - $this->modelId = $modelId; - if (!$pollingOptions) { - $pollingOptions = new PollingOptions(); - } - $this->pollingOptions = $pollingOptions; + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + $this->rag = $rag; $this->rawText = $rawText; $this->polygon = $polygon; $this->confidence = $confidence; - - if (isset($alias)) { - $this->alias = $alias; - } if (isset($textContext)) { $this->textContext = $textContext; } - if (isset($webhooksIds)) { - $this->webhooksIds = $webhooksIds; - } else { - $this->webhooksIds = []; - } if (isset($dataSchema)) { $this->dataSchema = new DataSchema($dataSchema); } diff --git a/src/Input/LocalResponse.php b/src/Input/LocalResponse.php index 81e037f8..9a16086d 100644 --- a/src/Input/LocalResponse.php +++ b/src/Input/LocalResponse.php @@ -117,7 +117,7 @@ public function isValidHMACSignature(string $secretKey, string $signature): bool } /** - * Deserialize the loaded local response into the requested CommonResponse-derived class. + * Deserialize the loaded local response into the requested BaseResponse-derived class. * * Typically used when dealing with V2 webhook callbacks. * diff --git a/src/Parsing/V2/CommonResponse.php b/src/Parsing/V2/BaseResponse.php similarity index 68% rename from src/Parsing/V2/CommonResponse.php rename to src/Parsing/V2/BaseResponse.php index 7e36eb27..783f7c3e 100644 --- a/src/Parsing/V2/CommonResponse.php +++ b/src/Parsing/V2/BaseResponse.php @@ -5,7 +5,7 @@ /** * Common response base class for V2. */ -abstract class CommonResponse +abstract class BaseResponse { /** * @var array Raw HTTP response from the server. @@ -13,11 +13,11 @@ abstract class CommonResponse private array $rawHttp; /** - * @param array $serverResponse Raw server response array. + * @param array $rawResponse Raw server response array. */ - protected function __construct(array $serverResponse) + protected function __construct(array $rawResponse) { - $this->rawHttp = $serverResponse; + $this->rawHttp = $rawResponse; } /** diff --git a/src/Parsing/V2/Inference.php b/src/Parsing/V2/Inference.php index f93537d7..81dfecaa 100644 --- a/src/Parsing/V2/Inference.php +++ b/src/Parsing/V2/Inference.php @@ -2,31 +2,14 @@ namespace Mindee\Parsing\V2; +use Mindee\Parsing\Common\SummaryHelper; +use Mindee\V2\Parsing\BaseInference; + /** * Inference class. */ -class Inference +class Inference extends BaseInference { - /** - * @var string ID of the inference. - */ - public string $id; - - /** - * @var InferenceJob Job the inference belongs to. - */ - public InferenceJob $job; - - /** - * @var InferenceModel Model info for the inference. - */ - public InferenceModel $model; - - /** - * @var InferenceFile File info for the inference. - */ - public InferenceFile $file; - /** * @var InferenceActiveOptions Active options for the inference. */ @@ -38,28 +21,26 @@ class Inference public InferenceResult $result; /** - * @param array $serverResponse Raw server response array. + * @param array $rawResponse Raw server response array. */ - public function __construct(array $serverResponse) + public function __construct(array $rawResponse) { - $this->id = $serverResponse['id']; - $this->job = new InferenceJob($serverResponse['job']); - $this->model = new InferenceModel($serverResponse['model']); - $this->file = new InferenceFile($serverResponse['file']); - $this->activeOptions = new InferenceActiveOptions($serverResponse['active_options']); - $this->result = new InferenceResult($serverResponse['result']); + parent::__construct($rawResponse); + $this->activeOptions = new InferenceActiveOptions($rawResponse['active_options']); + $this->result = new InferenceResult($rawResponse['result']); } /** * @return string String representation. */ + /** + * A prettier representation. + * @return string + */ public function __toString(): string { - return "Inference\n#########\n" - . "{$this->job}\n" - . "{$this->model}\n" - . "{$this->file}\n" - . "{$this->activeOptions}\n" - . "{$this->result}\n"; + $str = parent::__toString() . "$this->activeOptions\n\n$this->result\n"; + + return SummaryHelper::cleanOutString($str); } } diff --git a/src/Parsing/V2/InferenceResponse.php b/src/Parsing/V2/InferenceResponse.php index 27642c7c..fc86b2e9 100644 --- a/src/Parsing/V2/InferenceResponse.php +++ b/src/Parsing/V2/InferenceResponse.php @@ -5,7 +5,7 @@ /** * Inference response class for V2. */ -class InferenceResponse extends CommonResponse +class InferenceResponse extends BaseResponse { /** * @var Inference Inference result. @@ -13,11 +13,11 @@ class InferenceResponse extends CommonResponse public Inference $inference; /** - * @param array $serverResponse Raw server response array. + * @param array $rawResponse Raw server response array. */ - public function __construct(array $serverResponse) + public function __construct(array $rawResponse) { - parent::__construct($serverResponse); - $this->inference = new Inference($serverResponse['inference']); + parent::__construct($rawResponse); + $this->inference = new Inference($rawResponse['inference']); } } diff --git a/src/Parsing/V2/JobResponse.php b/src/Parsing/V2/JobResponse.php index 536e2c90..5f61841c 100644 --- a/src/Parsing/V2/JobResponse.php +++ b/src/Parsing/V2/JobResponse.php @@ -5,7 +5,7 @@ /** * Job response class. */ -class JobResponse extends CommonResponse +class JobResponse extends BaseResponse { /** * @var Job Job for the polling. @@ -13,11 +13,11 @@ class JobResponse extends CommonResponse public Job $job; /** - * @param array $serverResponse Raw server response array. + * @param array $rawResponse Raw server response array. */ - public function __construct(array $serverResponse) + public function __construct(array $rawResponse) { - parent::__construct($serverResponse); - $this->job = new Job($serverResponse['job']); + parent::__construct($rawResponse); + $this->job = new Job($rawResponse['job']); } } diff --git a/src/V2/ClientOptions/BaseParameters.php b/src/V2/ClientOptions/BaseParameters.php new file mode 100644 index 00000000..3bc5a222 --- /dev/null +++ b/src/V2/ClientOptions/BaseParameters.php @@ -0,0 +1,60 @@ + Optional webhook IDs. + */ + public array $webhooksIds; + + /** + * @var PollingOptions Polling options. + */ + public PollingOptions $pollingOptions; + + /** + * @var string Slug of the product. + */ + public static string $slug; + + /** + * @param string $modelId ID of the model. + * @param string|null $alias Optional file alias. + * @param array|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct(string $modelId, ?string $alias, ?array $webhooksIds, ?PollingOptions $pollingOptions) + { + $this->modelId = $modelId; + + if (isset($alias)) { + $this->alias = $alias; + } + if (isset($webhooksIds)) { + $this->webhooksIds = $webhooksIds; + } else { + $this->webhooksIds = []; + } + if (!$pollingOptions) { + $pollingOptions = new PollingOptions(); + } + $this->pollingOptions = $pollingOptions; + } +} diff --git a/src/V2/Parsing/BaseInference.php b/src/V2/Parsing/BaseInference.php new file mode 100644 index 00000000..27dcb0f9 --- /dev/null +++ b/src/V2/Parsing/BaseInference.php @@ -0,0 +1,55 @@ +id = $rawResponse['id']; + $this->model = new InferenceModel($rawResponse['model']); + $this->file = new InferenceFile($rawResponse['file']); + $this->job = new InferenceJob($rawResponse['job']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $str = "Inference\n#########\n$this->job\n\n$this->model\n\n$this->file\n\n"; + + return SummaryHelper::cleanOutString($str); + } +} diff --git a/src/V2/Product/Classification/ClassificationClassifier.php b/src/V2/Product/Classification/ClassificationClassifier.php new file mode 100644 index 00000000..59913398 --- /dev/null +++ b/src/V2/Product/Classification/ClassificationClassifier.php @@ -0,0 +1,30 @@ +documentType = $rawPrediction['document_type']; + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return "Document Type: $this->documentType"; + } +} diff --git a/src/V2/Product/Classification/ClassificationInference.php b/src/V2/Product/Classification/ClassificationInference.php new file mode 100644 index 00000000..f559dc74 --- /dev/null +++ b/src/V2/Product/Classification/ClassificationInference.php @@ -0,0 +1,39 @@ +result = new ClassificationResult($rawResponse['result']); + } + + + /** + * @return string String representation. + */ + /** + * A prettier representation. + * @return string + */ + public function __toString(): string + { + return SummaryHelper::cleanOutString(parent::__toString() . "$this->result\n"); + } +} diff --git a/src/V2/Product/Classification/ClassificationResponse.php b/src/V2/Product/Classification/ClassificationResponse.php new file mode 100644 index 00000000..db684191 --- /dev/null +++ b/src/V2/Product/Classification/ClassificationResponse.php @@ -0,0 +1,25 @@ +inference = new ClassificationInference($rawResponse['inference']); + } +} diff --git a/src/V2/Product/Classification/ClassificationResult.php b/src/V2/Product/Classification/ClassificationResult.php new file mode 100644 index 00000000..c7d43c64 --- /dev/null +++ b/src/V2/Product/Classification/ClassificationResult.php @@ -0,0 +1,30 @@ +classification = new ClassificationClassifier($rawResponse['classifier']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return "Classification\n==============\n" . $this->classification; + } +} diff --git a/src/V2/Product/Classification/Params/ClassificationParameters.php b/src/V2/Product/Classification/Params/ClassificationParameters.php new file mode 100644 index 00000000..c3191c94 --- /dev/null +++ b/src/V2/Product/Classification/Params/ClassificationParameters.php @@ -0,0 +1,32 @@ +|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct( + string $modelId, + ?string $alias = null, + ?array $webhooksIds = null, + ?PollingOptions $pollingOptions = null + ) { + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + } +} diff --git a/src/V2/Product/Crop/CropInference.php b/src/V2/Product/Crop/CropInference.php new file mode 100644 index 00000000..9eb13d07 --- /dev/null +++ b/src/V2/Product/Crop/CropInference.php @@ -0,0 +1,33 @@ +result = new CropResult($rawResponse['result']); + } + + /** + * @return string Raw server response array. + */ + public function __toString(): string + { + return parent::__toString() . "$this->result\n"; + } +} diff --git a/src/V2/Product/Crop/CropItem.php b/src/V2/Product/Crop/CropItem.php new file mode 100644 index 00000000..d38a11fa --- /dev/null +++ b/src/V2/Product/Crop/CropItem.php @@ -0,0 +1,38 @@ +location = new FieldLocation($rawResponse['location']); + $this->objectType = $rawResponse['object_type']; + } + + /** + * @return string String representation. + */ + public function __toString() + { + return "* :Location: $this->location\n :Object Type: $this->objectType"; + } +} diff --git a/src/V2/Product/Crop/CropResponse.php b/src/V2/Product/Crop/CropResponse.php new file mode 100644 index 00000000..033cd1c9 --- /dev/null +++ b/src/V2/Product/Crop/CropResponse.php @@ -0,0 +1,25 @@ +inference = new CropInference($rawResponse['inference']); + } +} diff --git a/src/V2/Product/Crop/CropResult.php b/src/V2/Product/Crop/CropResult.php new file mode 100644 index 00000000..966b46af --- /dev/null +++ b/src/V2/Product/Crop/CropResult.php @@ -0,0 +1,30 @@ +crops = array_map(fn ($crop) => new CropItem($crop), $rawResponse['crops']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return "Crops\n=====\n" . implode("\n", $this->crops); + } +} diff --git a/src/V2/Product/Crop/Params/CropParameters.php b/src/V2/Product/Crop/Params/CropParameters.php new file mode 100644 index 00000000..f7547cc1 --- /dev/null +++ b/src/V2/Product/Crop/Params/CropParameters.php @@ -0,0 +1,32 @@ +|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct( + string $modelId, + ?string $alias = null, + ?array $webhooksIds = null, + ?PollingOptions $pollingOptions = null + ) { + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + } +} diff --git a/src/V2/Product/Ocr/OcrInference.php b/src/V2/Product/Ocr/OcrInference.php new file mode 100644 index 00000000..6c6a5e4c --- /dev/null +++ b/src/V2/Product/Ocr/OcrInference.php @@ -0,0 +1,33 @@ +result = new OcrResult($rawResponse['result']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return parent::__toString() . "$this->result\n"; + } +} diff --git a/src/V2/Product/Ocr/OcrPage.php b/src/V2/Product/Ocr/OcrPage.php new file mode 100644 index 00000000..55293240 --- /dev/null +++ b/src/V2/Product/Ocr/OcrPage.php @@ -0,0 +1,42 @@ +words = array_map(fn ($word) => new OcrWord($word), $rawResponse['words']); + $this->content = $rawResponse['content']; + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $ocrWords = "\n"; + + if (!empty($this->words)) { + $ocrWords .= implode("\n\n", $this->words); + } + + return "OCR Words\n---------$ocrWords\n\n:Content: $this->content"; + } +} diff --git a/src/V2/Product/Ocr/OcrResponse.php b/src/V2/Product/Ocr/OcrResponse.php new file mode 100644 index 00000000..fc657c05 --- /dev/null +++ b/src/V2/Product/Ocr/OcrResponse.php @@ -0,0 +1,25 @@ +inference = new OcrInference($rawResponse['inference']); + } +} diff --git a/src/V2/Product/Ocr/OcrResult.php b/src/V2/Product/Ocr/OcrResult.php new file mode 100644 index 00000000..56b60128 --- /dev/null +++ b/src/V2/Product/Ocr/OcrResult.php @@ -0,0 +1,41 @@ +pages = array_map(fn ($page) => new OcrPage($page), $rawResponse['pages']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $str = "OCR Result\n##########\n"; + $i = 1; + + foreach ($this->pages as $page) { + $pageNumberTitle = "Page $i"; + $underline = str_repeat("=", strlen($pageNumberTitle)); + + $str .= "$pageNumberTitle\n$underline\n\n$page\n"; + $i++; + } + + return $str; + } +} diff --git a/src/V2/Product/Ocr/OcrWord.php b/src/V2/Product/Ocr/OcrWord.php new file mode 100644 index 00000000..48c070ab --- /dev/null +++ b/src/V2/Product/Ocr/OcrWord.php @@ -0,0 +1,38 @@ +content = $rawResponse['content']; + $this->polygon = new Polygon($rawResponse['polygon']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return $this->content; + } +} diff --git a/src/V2/Product/Ocr/Params/OcrParams.php b/src/V2/Product/Ocr/Params/OcrParams.php new file mode 100644 index 00000000..17edb563 --- /dev/null +++ b/src/V2/Product/Ocr/Params/OcrParams.php @@ -0,0 +1,32 @@ +|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct( + string $modelId, + ?string $alias = null, + ?array $webhooksIds = null, + ?PollingOptions $pollingOptions = null + ) { + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + } +} diff --git a/src/V2/Product/Split/Params/SplitParameters.php b/src/V2/Product/Split/Params/SplitParameters.php new file mode 100644 index 00000000..7d0d61f2 --- /dev/null +++ b/src/V2/Product/Split/Params/SplitParameters.php @@ -0,0 +1,32 @@ +|null $webhooksIds List of webhook IDs. + * @param PollingOptions|null $pollingOptions Polling options. + */ + public function __construct( + string $modelId, + ?string $alias = null, + ?array $webhooksIds = null, + ?PollingOptions $pollingOptions = null + ) { + parent::__construct($modelId, $alias, $webhooksIds, $pollingOptions); + } +} diff --git a/src/V2/Product/Split/SplitInference.php b/src/V2/Product/Split/SplitInference.php new file mode 100644 index 00000000..f1071710 --- /dev/null +++ b/src/V2/Product/Split/SplitInference.php @@ -0,0 +1,33 @@ +result = new SplitResult($rawResponse['result']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + return parent::__toString() . "$this->result\n"; + } +} diff --git a/src/V2/Product/Split/SplitRange.php b/src/V2/Product/Split/SplitRange.php new file mode 100644 index 00000000..d1d12d27 --- /dev/null +++ b/src/V2/Product/Split/SplitRange.php @@ -0,0 +1,39 @@ +pageRange = $rawResponse['page_range']; + $this->documentType = $rawResponse['document_type']; + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $pageRangeStr = implode(",", $this->pageRange); + + return "* :Page Range: $pageRangeStr\n :Document Type: $this->documentType"; + } +} diff --git a/src/V2/Product/Split/SplitResponse.php b/src/V2/Product/Split/SplitResponse.php new file mode 100644 index 00000000..f5c00076 --- /dev/null +++ b/src/V2/Product/Split/SplitResponse.php @@ -0,0 +1,25 @@ +inference = new SplitInference($rawResponse['inference']); + } +} diff --git a/src/V2/Product/Split/SplitResult.php b/src/V2/Product/Split/SplitResult.php new file mode 100644 index 00000000..aa61bb46 --- /dev/null +++ b/src/V2/Product/Split/SplitResult.php @@ -0,0 +1,32 @@ +splits = array_map(fn ($split) => new SplitRange($split), $rawResponse['splits']); + } + + /** + * @return string String representation. + */ + public function __toString(): string + { + $splitsStr = implode("\n", $this->splits); + + return "Splits\n======\n$splitsStr"; + } +} From faa0baf4b779c32d013800809a3d817654280016 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:59:05 +0100 Subject: [PATCH 02/17] modify API behavior to take new products into account --- src/ClientV2.php | 58 +++++- src/Http/MindeeApiV2.php | 228 +++++++++++++++------- src/Parsing/V2/InferenceActiveOptions.php | 2 +- src/Parsing/V2/InferenceFile.php | 2 +- src/Parsing/V2/InferenceJob.php | 2 +- src/Parsing/V2/InferenceModel.php | 2 +- tests/V2/ClientV2Test.php | 2 +- tests/resources | 2 +- 8 files changed, 213 insertions(+), 85 deletions(-) diff --git a/src/ClientV2.php b/src/ClientV2.php index 651f7ee1..78e3c861 100644 --- a/src/ClientV2.php +++ b/src/ClientV2.php @@ -6,8 +6,10 @@ use Mindee\Http\MindeeApiV2; use Mindee\Input\InferenceParameters; use Mindee\Input\InputSource; +use Mindee\Parsing\V2\BaseResponse; use Mindee\Parsing\V2\InferenceResponse; use Mindee\Parsing\V2\JobResponse; +use Mindee\V2\ClientOptions\BaseParameters; /** * Mindee Client V2. @@ -44,7 +46,22 @@ public function enqueueInference( InputSource $inputSource, InferenceParameters $params ): JobResponse { - return $this->mindeeApi->reqPostInferenceEnqueue($inputSource, $params); + return $this->enqueue($inputSource, $params); + } + + /** + * Send the document to an asynchronous endpoint and return its ID in the queue. + * @param InputSource $inputSource File to parse. + * @param BaseParameters $params Parameters relating to prediction options. + * @return JobResponse A JobResponse containing the job (queue) corresponding to a document. + * @throws MindeeException Throws if the input document is not provided. + * @category Asynchronous + */ + public function enqueue( + InputSource $inputSource, + BaseParameters $params + ): JobResponse { + return $this->mindeeApi->reqPostEnqueue($inputSource, $params); } /** @@ -59,6 +76,20 @@ public function getInference(string $inferenceId): InferenceResponse return $this->mindeeApi->reqGetInference($inferenceId); } + /** + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param string $jobUrl URL of the job. + * @return T A response containing parsing results. + */ + public function getResultFromUrl( + string $responseClass, + string $jobUrl + ): BaseResponse { + return $this->mindeeApi->reqGetResultFromUrl($responseClass, $jobUrl); + } + /** * Get the status of an inference that was previously enqueued. * Can be used for polling. @@ -80,15 +111,34 @@ public function getJob(string $jobId): JobResponse * @param InferenceParameters $params Parameters relating to prediction options. * @return InferenceResponse A response containing parsing results. * @throws MindeeException Throws if enqueueing fails, job fails, or times out. - * @category Synchronous */ public function enqueueAndGetInference( InputSource $inputDoc, InferenceParameters $params ): InferenceResponse { + return $this->enqueueAndGetResult(InferenceResponse::class, $inputDoc, $params); + } + + /** + * Send a document to an endpoint and poll the server until the result is sent or + * until the maximum number of tries is reached. + * + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param InputSource $inputDoc Input document to parse. + * @param BaseParameters $params Parameters relating to prediction options. + * @return T A response containing parsing results. + * @throws MindeeException Throws if enqueueing fails, job fails, or times out. + */ + public function enqueueAndGetResult( + string $responseClass, + InputSource $inputDoc, + BaseParameters $params + ): BaseResponse { $pollingOptions = $params->pollingOptions; - $enqueueResponse = $this->enqueueInference($inputDoc, $params); + $enqueueResponse = $this->enqueue($inputDoc, $params); if (empty($enqueueResponse->job->id)) { error_log("Failed enqueueing:\n" . json_encode($enqueueResponse)); @@ -107,7 +157,7 @@ public function enqueueAndGetInference( break; } if ($pollResults->job->status === "Processed") { - return $this->getInference($pollResults->job->id); + return $this->getResultFromUrl($responseClass, $pollResults->job->pollingUrl); } error_log( diff --git a/src/Http/MindeeApiV2.php b/src/Http/MindeeApiV2.php index 81ce6f7a..3aefc07a 100644 --- a/src/Http/MindeeApiV2.php +++ b/src/Http/MindeeApiV2.php @@ -6,8 +6,10 @@ namespace Mindee\Http; +use CurlHandle; use Exception; use Mindee\Error\ErrorCode; +use Mindee\Error\MindeeApiException; use Mindee\Error\MindeeException; // phpcs:disable @@ -21,9 +23,14 @@ use Mindee\Input\InputSource; use Mindee\Input\LocalInputSource; use Mindee\Input\URLInputSource; +use Mindee\Parsing\V2\BaseResponse; use Mindee\Parsing\V2\ErrorResponse; use Mindee\Parsing\V2\InferenceResponse; use Mindee\Parsing\V2\JobResponse; +use Mindee\V2\ClientOptions\BaseParameters; +use ReflectionClass; +use ReflectionException; +use ReflectionProperty; use const Mindee\VERSION; @@ -62,13 +69,10 @@ class MindeeApiV2 */ private function getUserAgent(): string { - switch (PHP_OS_FAMILY) { - case "Darwin": - $os = "macos"; - break; - default: - $os = strtolower(PHP_OS_FAMILY); - } + $os = match (PHP_OS_FAMILY) { + "Darwin" => "macos", + default => strtolower(PHP_OS_FAMILY), + }; return 'mindee-api-php@v' . VERSION . ' php-v' . PHP_VERSION . ' ' . $os; } @@ -156,43 +160,64 @@ protected function setApiKey(?string $apiKey = null): void } /** - * @param InputSource $inputDoc Input document. - * @param InferenceParameters $params Parameters for the inference. + * @param InputSource $inputDoc Input document. + * @param BaseParameters $params Parameters for the inference. * @return JobResponse Server response wrapped in a JobResponse object. * @throws MindeeException Throws if the model ID is not provided. */ - public function reqPostInferenceEnqueue(InputSource $inputDoc, InferenceParameters $params): JobResponse + public function reqPostEnqueue(InputSource $inputDoc, BaseParameters $params): JobResponse { if (!isset($params->modelId)) { throw new MindeeException("Model ID must be provided.", ErrorCode::USER_INPUT_ERROR); } $response = $this->documentEnqueuePost($inputDoc, $params); - return $this->processResponse($response, JobResponse::class); + return $this->processJobResponse($response); } /** * Process the HTTP response and return the appropriate response object. * - * @param array $result Raw HTTP response array with 'data' and 'code' keys. - * @param string $responseType Class name of the response type to instantiate. - * @return JobResponse|InferenceResponse The processed response object. + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param array $result Raw HTTP response array with 'data' and 'code' keys. + * @return T A response containing parsing results. * @throws MindeeException Throws if HTTP status indicates an error or deserialization fails. - * @throws MindeeV2HttpException Throws if the HTTP status indicates an error. - * @throws MindeeV2HttpUnknownException Throws if the server sends an unexpected reply. */ - private function processResponse(array $result, string $responseType): InferenceResponse|JobResponse - { - $statusCode = $result['code'] ?? -1; + private function processResponse( + string $responseClass, + array $result + ): BaseResponse { + $this->checkValidResponse($result); - if ($statusCode > 399 || $statusCode < 200) { + try { $responseData = json_decode($result['data'], true); - - if ($responseData && isset($responseData['status'])) { - throw new MindeeV2HttpException(new ErrorResponse($responseData)); + $reflectionClass = new ReflectionClass($responseClass); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new MindeeException('JSON decode error: ' . json_last_error_msg()); } - throw new MindeeV2HttpUnknownException(json_encode($result, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + + /** @var T $instance */ + $instance = $reflectionClass->newInstance($responseData); + return $instance; + } catch (Exception $e) { + error_log("Raised '{$e->getMessage()}' Couldn't deserialize response object:\n" . $result['data']); + throw new MindeeException("Couldn't deserialize response object.", ErrorCode::API_UNPROCESSABLE_ENTITY); } + } + + /** + * Process the HTTP response and return the appropriate response object. + * + * @param array $result Raw HTTP response array with 'data' and 'code' keys. + * @return JobResponse The processed response object. + * @throws MindeeException Throws if HTTP status indicates an error or deserialization fails. + * @throws MindeeApiException Throws if the response type is not recognized. + */ + private function processJobResponse(array $result): JobResponse + { + $this->checkValidResponse($result); try { $responseData = json_decode($result['data'], true); @@ -200,10 +225,10 @@ private function processResponse(array $result, string $responseType): Inference throw new MindeeException('JSON decode error: ' . json_last_error_msg()); } - return new $responseType($responseData); + return new JobResponse($responseData); } catch (Exception $e) { - error_log("Raised '{$e->getMessage()}' Couldn't deserialize response object:\n" . $result['data']); - throw new MindeeException("Couldn't deserialize response object.", ErrorCode::API_UNPROCESSABLE_ENTITY); + error_log("Raised '{$e->getMessage()}' Couldn't deserialize job response:\n" . $result['data']); + throw new MindeeApiException("Couldn't deserialize response object.", ErrorCode::API_UNPROCESSABLE_ENTITY); } } @@ -220,8 +245,7 @@ public function reqGetInference(string $inferenceId): InferenceResponse if (!isset($inferenceId)) { throw new MindeeException("Inference ID must be provided.", ErrorCode::USER_INPUT_ERROR); } - $response = $this->inferenceGetRequest($inferenceId); - return $this->processResponse($response, InferenceResponse::class); + return $this->reqGetResult(InferenceResponse::class, $inferenceId); } /** @@ -237,8 +261,58 @@ public function reqGetJob(string $jobId): JobResponse if (!isset($jobId)) { throw new MindeeException("Inference ID must be provided.", ErrorCode::USER_INPUT_ERROR); } - $response = $this->jobGetRequest($jobId); - return $this->processResponse($response, JobResponse::class); + $response = $this->sendGetRequest($this->baseUrl . "/jobs/$jobId"); + return $this->processJobResponse($response, JobResponse::class); + } + + + /** + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param string $resultId URL of the result. + * @return T A response containing parsing results. + * @throws MindeeException Throws if the server's response contains an error. + * @throws MindeeApiException Throws if the response class is not valid. + */ + public function reqGetResult( + string $responseClass, + string $resultId + ): BaseResponse { + if (!isset($responseClass) || !isset($resultId)) { + throw new MindeeException("Response class and job ID must be provided.", ErrorCode::USER_INPUT_ERROR); + } + + try { + $slug = new ReflectionProperty($responseClass, 'slug'); + } catch (ReflectionException $e) { + throw new MindeeApiException( + "Unable to access slug property of " . $responseClass, + ErrorCode::INTERNAL_LIBRARY_ERROR, + $e + ); + } + $response = $this->sendGetRequest($this->baseUrl . "/products/$resultId/results/$slug"); + return $this->processResponse($responseClass, $response); + } + + /** + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param string $resultUrl URL of the result. + * @return T A response containing parsing results. + * @throws MindeeException Throws if the server's response contains an error. + */ + public function reqGetResultFromUrl( + string $responseClass, + string $resultUrl + ): BaseResponse { + if (!isset($responseClass) || !isset($resultUrl)) { + throw new MindeeException("Response class and result URL must be provided.", ErrorCode::USER_INPUT_ERROR); + } + $response = $this->sendGetRequest($resultUrl); + return $this->processResponse($responseClass, $response); } /** @@ -266,32 +340,14 @@ private function initChannel() /** * Makes a GET call to retrieve a job. - * @param string $jobId ID of the job. + * @param string $url URL of the job. * @return array Server response. */ - private function jobGetRequest(string $jobId): array + private function sendGetRequest(string $url): array { + /** @var CurlHandle $ch */ $ch = $this->initChannel(); - curl_setopt($ch, CURLOPT_URL, $this->baseUrl . "/jobs/$jobId"); - $resp = [ - 'data' => curl_exec($ch), - 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), - ]; - curl_close($ch); - - return $resp; - } - - /** - * Makes a GET call to retrieve an inference. - * @param string $inferenceId ID of the inference. - * @return array Server response. - */ - private function inferenceGetRequest(string $inferenceId): array - { - $ch = $this->initChannel(); - curl_setopt($ch, CURLOPT_URL, $this->baseUrl . "/inferences/$inferenceId"); - + curl_setopt($ch, CURLOPT_URL, $url); $resp = [ 'data' => curl_exec($ch), 'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), @@ -304,15 +360,16 @@ private function inferenceGetRequest(string $inferenceId): array /** * Starts a CURL session using POST. * - * @param InputSource $inputSource File to upload. - * @param InferenceParameters $params Inference parameters. + * @param InputSource $inputSource File to upload. + * @param BaseParameters $params Inference parameters. * @return array * @throws MindeeException Throws if the cURL operation doesn't go succeed. */ private function documentEnqueuePost( InputSource $inputSource, - InferenceParameters $params + BaseParameters $params ): array { + /** @var CurlHandle $ch */ $ch = $this->initChannel(); $postFields = ['model_id' => $params->modelId]; if ($inputSource instanceof URLInputSource) { @@ -322,17 +379,25 @@ private function documentEnqueuePost( $postFields['file'] = $inputSource->fileObject; } - if (isset($params->rawText)) { - $postFields['raw_text'] = $params->rawText ? 'true' : 'false'; - } - if (isset($params->polygon)) { - $postFields['polygon'] = $params->polygon ? 'true' : 'false'; - } - if (isset($params->confidence)) { - $postFields['confidence'] = $params->confidence ? 'true' : 'false'; - } - if (isset($params->rag)) { - $postFields['rag'] = $params->rag ? 'true' : 'false'; + if (is_a($params, InferenceParameters::class)) { + if (isset($params->rawText)) { + $postFields['raw_text'] = $params->rawText ? 'true' : 'false'; + } + if (isset($params->polygon)) { + $postFields['polygon'] = $params->polygon ? 'true' : 'false'; + } + if (isset($params->confidence)) { + $postFields['confidence'] = $params->confidence ? 'true' : 'false'; + } + if (isset($params->rag)) { + $postFields['rag'] = $params->rag ? 'true' : 'false'; + } + if (isset($params->textContext)) { + $postFields['text_context'] = $params->textContext; + } + if (isset($params->dataSchema)) { + $postFields['data_schema'] = strval($params->dataSchema); + } } if (isset($params->webhooksIds) && count($params->webhooksIds) > 0) { if (PHP_VERSION_ID < 80200 && count($params->webhooksIds) > 1) { @@ -347,14 +412,7 @@ private function documentEnqueuePost( if (isset($params->alias)) { $postFields['alias'] = $params->alias; } - if (isset($params->textContext)) { - $postFields['text_context'] = $params->textContext; - } - if (isset($params->dataSchema)) { - $postFields['data_schema'] = strval($params->dataSchema); - } - - $url = $this->baseUrl . '/inferences/enqueue'; + $url = $this->baseUrl . "products/{$params::$slug}/enqueue"; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); $resp = [ @@ -370,4 +428,24 @@ private function documentEnqueuePost( return $resp; } + + /** + * @param array $result Raw HTTP response array with 'data' and 'code' keys. + * @return void + * @throws MindeeV2HttpException Throws if the HTTP status indicates an error. + * @throws MindeeV2HttpUnknownException Throws if the server sends an unexpected reply. + */ + private function checkValidResponse(array $result): void + { + $statusCode = $result['code'] ?? -1; + + if ($statusCode > 399 || $statusCode < 200) { + $responseData = json_decode($result['data'], true); + + if ($responseData && isset($responseData['status'])) { + throw new MindeeV2HttpException(new ErrorResponse($responseData)); + } + throw new MindeeV2HttpUnknownException(json_encode($result, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + } + } } diff --git a/src/Parsing/V2/InferenceActiveOptions.php b/src/Parsing/V2/InferenceActiveOptions.php index 0e4e82ae..743cc4c8 100644 --- a/src/Parsing/V2/InferenceActiveOptions.php +++ b/src/Parsing/V2/InferenceActiveOptions.php @@ -70,6 +70,6 @@ public function __toString(): string . ':Confidence: ' . SummaryHelper::formatForDisplay($this->confidence) . "\n" . ':RAG: ' . SummaryHelper::formatForDisplay($this->rag) . "\n" . ':Text Context: ' . SummaryHelper::formatForDisplay($this->textContext) . "\n\n" - . $this->dataSchema . "\n"; + . $this->dataSchema; } } diff --git a/src/Parsing/V2/InferenceFile.php b/src/Parsing/V2/InferenceFile.php index f5e1c699..20d1d0e1 100644 --- a/src/Parsing/V2/InferenceFile.php +++ b/src/Parsing/V2/InferenceFile.php @@ -47,6 +47,6 @@ public function __toString(): string . ":Name: $this->name\n" . ":Alias:" . ($this->alias ? " $this->alias" : '') . "\n" . ":Page Count: $this->pageCount\n" - . ":MIME Type: $this->mimeType\n"; + . ":MIME Type: $this->mimeType"; } } diff --git a/src/Parsing/V2/InferenceJob.php b/src/Parsing/V2/InferenceJob.php index a1d798a3..225c5697 100644 --- a/src/Parsing/V2/InferenceJob.php +++ b/src/Parsing/V2/InferenceJob.php @@ -26,6 +26,6 @@ public function __construct(array $serverResponse) public function __toString(): string { return "Job\n===\n" - . ":ID: {$this->id}\n" ; + . ":ID: $this->id" ; } } diff --git a/src/Parsing/V2/InferenceModel.php b/src/Parsing/V2/InferenceModel.php index 69309fce..b11d1c64 100644 --- a/src/Parsing/V2/InferenceModel.php +++ b/src/Parsing/V2/InferenceModel.php @@ -26,6 +26,6 @@ public function __construct(array $serverResponse) public function __toString(): string { return "Model\n=====\n" - . ":ID: {$this->id}\n" ; + . ":ID: $this->id" ; } } diff --git a/tests/V2/ClientV2Test.php b/tests/V2/ClientV2Test.php index 514ba492..3de066ed 100644 --- a/tests/V2/ClientV2Test.php +++ b/tests/V2/ClientV2Test.php @@ -31,7 +31,7 @@ public function testEnqueuePostAsync(): void $predictable = $this->createMock(MindeeApiV2::class); $syntheticResponse = file_get_contents(\TestingUtilities::getV2DataDir() . '/job/ok_processing.json'); $predictable->expects($this->once()) - ->method('reqPostInferenceEnqueue') + ->method('reqPostEnqueue') ->with( $this->isInstanceOf(LocalInputSource::class), $this->isInstanceOf(InferenceParameters::class) diff --git a/tests/resources b/tests/resources index c2e36f5b..53f0efbc 160000 --- a/tests/resources +++ b/tests/resources @@ -1 +1 @@ -Subproject commit c2e36f5b635386cb9bb922b517c4e02039b0a122 +Subproject commit 53f0efbc08c77c2c085aadd27de9d2d6c359276e From e13ee0a53ddd1c810b3184960e286bbb44cfd85b Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:31:13 +0100 Subject: [PATCH 03/17] add _some_ tests --- .github/workflows/_test-integrations.yml | 4 + src/Geometry/Polygon.php | 10 +- src/Parsing/V2/Field/FieldLocation.php | 2 +- .../Classification/ClassificationResult.php | 2 +- tests/V2/Product/ClassificationFunctional.php | 55 ++++++ tests/V2/Product/ClassificationTest.php | 66 +++++++ tests/V2/Product/CropTest.php | 168 ++++++++++++++++++ tests/V2/Product/OcrTest.php | 108 +++++++++++ tests/V2/Product/SplitTest.php | 107 +++++++++++ 9 files changed, 515 insertions(+), 7 deletions(-) create mode 100644 tests/V2/Product/ClassificationFunctional.php create mode 100644 tests/V2/Product/ClassificationTest.php create mode 100644 tests/V2/Product/CropTest.php create mode 100644 tests/V2/Product/OcrTest.php create mode 100644 tests/V2/Product/SplitTest.php diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 79bbbcee..91be5107 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -15,6 +15,10 @@ env: MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} MINDEE_V2_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} MINDEE_V2_SE_TESTS_BLANK_PDF_URL: ${{ secrets.MINDEE_V2_SE_TESTS_BLANK_PDF_URL }} + MINDEE_V2_CLASSIFICATION_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID }} + MINDEE_V2_CROP_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CROP_MODEL_ID }} + MINDEE_V2_OCR_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_OCR_MODEL_ID }} + MINDEE_V2_SPLIT_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID }} jobs: integration-tests-ubuntu: diff --git a/src/Geometry/Polygon.php b/src/Geometry/Polygon.php index 69ca0ccb..339086d0 100644 --- a/src/Geometry/Polygon.php +++ b/src/Geometry/Polygon.php @@ -160,11 +160,11 @@ public function getCoordinates(): ?array /** * @return string String representation. */ - public function __toString() + public function __toString(): string { - if (!$this->isEmpty()) { - return 'Polygon with ' . count($this->getCoordinates()) . ' points.'; - } - return ''; + $formattedPoints = array_map(fn ($p) => "({$p->getX()},{$p->getY()})", $this->coordinates); + $joinedPoints = implode(", ", $formattedPoints); + + return "($joinedPoints)"; } } diff --git a/src/Parsing/V2/Field/FieldLocation.php b/src/Parsing/V2/Field/FieldLocation.php index 2f278ee0..e954a17b 100644 --- a/src/Parsing/V2/Field/FieldLocation.php +++ b/src/Parsing/V2/Field/FieldLocation.php @@ -39,6 +39,6 @@ public function __construct(array $serverResponse) */ public function __toString(): string { - return $this->polygon ? (string)$this->polygon : ''; + return $this->polygon ? $this->polygon . " on page $this->page" : ''; } } diff --git a/src/V2/Product/Classification/ClassificationResult.php b/src/V2/Product/Classification/ClassificationResult.php index c7d43c64..9c9e8f7b 100644 --- a/src/V2/Product/Classification/ClassificationResult.php +++ b/src/V2/Product/Classification/ClassificationResult.php @@ -17,7 +17,7 @@ class ClassificationResult */ public function __construct(array $rawResponse) { - $this->classification = new ClassificationClassifier($rawResponse['classifier']); + $this->classification = new ClassificationClassifier($rawResponse['classification']); } /** diff --git a/tests/V2/Product/ClassificationFunctional.php b/tests/V2/Product/ClassificationFunctional.php new file mode 100644 index 00000000..a2279b3d --- /dev/null +++ b/tests/V2/Product/ClassificationFunctional.php @@ -0,0 +1,55 @@ +client = new ClientV2($apiKey); + + $this->classificationModelId = getenv('MINDEE_V2_CLASSIFICATION_MODEL_ID') ?: ''; + } + + /** + * Tests the success of the classification process using a default sample file. + * + * @return void + */ + public function testClassificationDefaultSampleMustSucceed(): void + { + $inputSource = new PathInput( + TestingUtilities::getV2ProductDir() . '/classification/default_invoice.jpg' + ); + + $productParams = new ClassificationParameters($this->classificationModelId); + $response = $this->client->enqueueAndGetResult(ClassificationResponse::class, $inputSource, $productParams); + + $this->assertNotNull($response); + $this->assertNotNull($response->inference); + + $file = $response->inference->file; + $this->assertNotNull($file); + $this->assertSame("default_invoice.jpg", $file->name); + + $result = $response->inference->result; + $this->assertNotNull($result); + + $classifications = $result->classification; + $this->assertNotNull($classifications); + } +} diff --git a/tests/V2/Product/ClassificationTest.php b/tests/V2/Product/ClassificationTest.php new file mode 100644 index 00000000..64d13de0 --- /dev/null +++ b/tests/V2/Product/ClassificationTest.php @@ -0,0 +1,66 @@ +assertNotNull($response->inference); + $this->assertNotNull($response->inference->id); + $this->assertNotNull($response->inference->file); + $this->assertNotNull($response->inference->result); + } + + /** + * Should correctly map properties when reading a single classification JSON. + * @return void + */ + public function testClassificationWhenSingleMustHaveValidProperties(): void + { + $jsonSample = self::getInference(); + + $response = new ClassificationResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $this->assertSame("12345678-1234-1234-1234-123456789abc", $inference->id); + $this->assertSame("test-model-id", $inference->model->id); + $this->assertSame("12345678-1234-1234-1234-jobid1234567", $inference->job->id); + + $this->assertSame("default_sample.jpg", $inference->file->name); + $this->assertSame(1, $inference->file->pageCount); + $this->assertSame("image/jpeg", $inference->file->mimeType); + + $classification = $inference->result->classification; + $this->assertSame("invoice", $classification->documentType); + } +} diff --git a/tests/V2/Product/CropTest.php b/tests/V2/Product/CropTest.php new file mode 100644 index 00000000..dbfd3d67 --- /dev/null +++ b/tests/V2/Product/CropTest.php @@ -0,0 +1,168 @@ +assertNotNull($response->inference); + $this->assertNotNull($response->inference->id); + $this->assertNotNull($response->inference->file); + $this->assertNotNull($response->inference->result); + } + + /** + * Ensures all line endings are identical before comparison so the test + * behaves the same on every platform (LF vs CRLF). + * @param string $input Input string to normalize. + * @return string + */ + private static function normalizeLineEndings(string $input): string + { + return str_replace(["\r\n", "\r"], "\n", $input); + } + + /** + * Should correctly map properties when reading a single crop JSON. + * @return void + */ + public function testCropWhenSingleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("crop/crop_single.json"); + $response = new CropResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $this->assertSame("12345678-1234-1234-1234-123456789abc", $inference->id); + $this->assertSame("test-model-id", $inference->model->id); + $this->assertSame("12345678-1234-1234-1234-jobid1234567", $inference->job->id); + + $this->assertSame("sample.jpeg", $inference->file->name); + $this->assertSame(1, $inference->file->pageCount); + $this->assertSame("image/jpeg", $inference->file->mimeType); + + $crops = $inference->result->crops; + $this->assertNotNull($crops); + $this->assertCount(1, $crops); + + $firstCrop = $crops[0]; + $this->assertSame("invoice", $firstCrop->objectType); + $this->assertSame(0, $firstCrop->location->page); + + $polygon = $firstCrop->location->polygon; + $this->assertCount(4, $polygon->getCoordinates()); + + // Note: Using assertEquals here instead of assertSame to allow for object value comparison + $this->assertEquals(new Point(0.15, 0.254), $polygon->getCoordinates()[0]); + $this->assertEquals(new Point(0.85, 0.254), $polygon->getCoordinates()[1]); + $this->assertEquals(new Point(0.85, 0.947), $polygon->getCoordinates()[2]); + $this->assertEquals(new Point(0.15, 0.947), $polygon->getCoordinates()[3]); + } + + /** + * Should correctly map properties when reading a multiple crop JSON. + * @return void + */ + public function testCropWhenMultipleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("crop/crop_multiple.json"); + $response = new CropResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $job = $inference->job; + $this->assertSame("12345678-1234-1234-1234-jobid1234567", $job->id); + + $this->assertSame("12345678-1234-1234-1234-123456789abc", $inference->id); + $this->assertSame("test-model-id", $inference->model->id); + + $this->assertSame("default_sample.jpg", $inference->file->name); + $this->assertSame(1, $inference->file->pageCount); + $this->assertSame("image/jpeg", $inference->file->mimeType); + + $crops = $inference->result->crops; + $this->assertNotNull($crops); + $this->assertCount(2, $crops); + + $firstCrop = $crops[0]; + $this->assertSame("invoice", $firstCrop->objectType); + $this->assertSame(0, $firstCrop->location->page); + + $firstPolygon = $firstCrop->location->polygon; + $this->assertCount(4, $firstPolygon->getCoordinates()); + $this->assertEquals(new Point(0.214, 0.079), $firstPolygon->getCoordinates()[0]); + $this->assertEquals(new Point(0.476, 0.079), $firstPolygon->getCoordinates()[1]); + $this->assertEquals(new Point(0.476, 0.979), $firstPolygon->getCoordinates()[2]); + $this->assertEquals(new Point(0.214, 0.979), $firstPolygon->getCoordinates()[3]); + + $secondCrop = $crops[1]; + $this->assertSame("receipt", $secondCrop->objectType); + $this->assertSame(0, $secondCrop->location->page); + + $secondPolygon = $secondCrop->location->polygon; + $this->assertCount(4, $secondPolygon->getCoordinates()); + $this->assertEquals(new Point(0.547, 0.15), $secondPolygon->getCoordinates()[0]); + $this->assertEquals(new Point(0.862, 0.15), $secondPolygon->getCoordinates()[1]); + $this->assertEquals(new Point(0.862, 0.97), $secondPolygon->getCoordinates()[2]); + $this->assertEquals(new Point(0.547, 0.97), $secondPolygon->getCoordinates()[3]); + } + + /** + * crop_single.rst – RST display must be parsed and exposed + * @return void + */ + public function testRstDisplayMustBeAccessible(): void + { + $jsonSample = self::getInference("crop/crop_single.json"); + $response = new CropResponse($jsonSample); + + $rstReferencePath = TestingUtilities::getV2ProductDir() . "/crop/crop_single.rst"; + $rstReference = file_get_contents($rstReferencePath); + + $inference = $response->inference; + $this->assertNotNull($inference); + + // Assumes your Inference class implements the __toString() magic method + // which maps to C#'s ToString() + $this->assertEquals( + self::normalizeLineEndings($rstReference), + self::normalizeLineEndings((string)$inference) + ); + } +} diff --git a/tests/V2/Product/OcrTest.php b/tests/V2/Product/OcrTest.php new file mode 100644 index 00000000..01d027b5 --- /dev/null +++ b/tests/V2/Product/OcrTest.php @@ -0,0 +1,108 @@ +assertNotNull($response->inference); + $this->assertNotNull($response->inference->id); + $this->assertNotNull($response->inference->file); + $this->assertNotNull($response->inference->result); + } + + /** + * Should correctly map properties when reading a single OCR JSON. + * @return void + */ + public function testOcrWhenSingleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("ocr/ocr_single.json"); + $response = new OcrResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $this->assertSame("12345678-1234-1234-1234-123456789abc", $inference->id); + $this->assertSame("test-model-id", $inference->model->id); + + $this->assertSame("default_sample.jpg", $inference->file->name); + $this->assertSame(1, $inference->file->pageCount); + $this->assertSame("image/jpeg", $inference->file->mimeType); + + $pages = $inference->result->pages; + $this->assertNotNull($pages); + $this->assertCount(1, $pages); + + $firstPage = $pages[0]; + $this->assertNotNull($firstPage->words); + + $firstWord = $firstPage->words[0]; + $this->assertSame("Shipper:", $firstWord->content); + // Using the getCoordinates() logic from the corrected file + $this->assertCount(4, $firstWord->polygon->getCoordinates()); + + $fifthWord = $firstPage->words[4]; + $this->assertSame("INC.", $fifthWord->content); + $this->assertCount(4, $fifthWord->polygon->getCoordinates()); + } + + /** + * Should correctly map properties when reading a multiple OCR JSON. + * @return void + */ + public function testOcrWhenMultipleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("ocr/ocr_multiple.json"); + $response = new OcrResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $job = $inference->job; + $this->assertSame("12345678-1234-1234-1234-jobid1234567", $job->id); + + $model = $inference->model; + $this->assertNotNull($model); + + $pages = $inference->result->pages; + $this->assertNotNull($pages); + $this->assertCount(3, $pages); + + foreach ($pages as $page) { + $this->assertNotNull($page->words); + $this->assertNotNull($page->content); + $this->assertIsString($page->content); + } + } +} diff --git a/tests/V2/Product/SplitTest.php b/tests/V2/Product/SplitTest.php new file mode 100644 index 00000000..b4925cb0 --- /dev/null +++ b/tests/V2/Product/SplitTest.php @@ -0,0 +1,107 @@ +assertNotNull($response->inference); + $this->assertNotNull($response->inference->id); + $this->assertNotNull($response->inference->file); + $this->assertNotNull($response->inference->result); + } + + /** + * Should correctly map properties when reading a single split JSON. + * @return void + */ + public function testSplitWhenSingleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("split/split_single.json"); + $response = new SplitResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $model = $inference->model; + $this->assertNotNull($model); + + $splits = $inference->result->splits; + $this->assertNotNull($splits); + $this->assertCount(1, $splits); + + $firstSplit = $splits[0]; + $this->assertSame("receipt", $firstSplit->documentType); + + $this->assertNotNull($firstSplit->pageRange); + $this->assertCount(2, $firstSplit->pageRange); + $this->assertSame(0, $firstSplit->pageRange[0]); + $this->assertSame(0, $firstSplit->pageRange[1]); + } + + /** + * Should correctly map properties when reading a multiple split JSON. + * @return void + */ + public function testSplitWhenMultipleMustHaveValidProperties(): void + { + $jsonSample = self::getInference("split/split_multiple.json"); + $response = new SplitResponse($jsonSample); + + $this->assertInferenceResponse($response); + + $inference = $response->inference; + + $model = $inference->model; + $this->assertNotNull($model); + + $splits = $inference->result->splits; + $this->assertNotNull($splits); + $this->assertCount(3, $splits); + + $firstSplit = $splits[0]; + $this->assertSame("passport", $firstSplit->documentType); + + $this->assertNotNull($firstSplit->pageRange); + $this->assertCount(2, $firstSplit->pageRange); + $this->assertSame(0, $firstSplit->pageRange[0]); + $this->assertSame(0, $firstSplit->pageRange[1]); + + $secondSplit = $splits[1]; + $this->assertSame("invoice", $secondSplit->documentType); + + $this->assertNotNull($secondSplit->pageRange); + $this->assertCount(2, $secondSplit->pageRange); + $this->assertSame(1, $secondSplit->pageRange[0]); + $this->assertSame(3, $secondSplit->pageRange[1]); + } +} From ae673af944dc5704494bc3fbe6b8f646c0265838 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 10:38:48 +0100 Subject: [PATCH 04/17] fix API path --- src/ClientV2.php | 2 +- src/Http/MindeeApiV2.php | 2 +- src/V2/ClientOptions/BaseParameters.php | 5 ----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ClientV2.php b/src/ClientV2.php index 78e3c861..6eda3b43 100644 --- a/src/ClientV2.php +++ b/src/ClientV2.php @@ -157,7 +157,7 @@ public function enqueueAndGetResult( break; } if ($pollResults->job->status === "Processed") { - return $this->getResultFromUrl($responseClass, $pollResults->job->pollingUrl); + return $this->getResultFromUrl($responseClass, $pollResults->job->resultUrl); } error_log( diff --git a/src/Http/MindeeApiV2.php b/src/Http/MindeeApiV2.php index 3aefc07a..67f0e804 100644 --- a/src/Http/MindeeApiV2.php +++ b/src/Http/MindeeApiV2.php @@ -412,7 +412,7 @@ private function documentEnqueuePost( if (isset($params->alias)) { $postFields['alias'] = $params->alias; } - $url = $this->baseUrl . "products/{$params::$slug}/enqueue"; + $url = $this->baseUrl . "/products/{$params::$slug}/enqueue"; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); $resp = [ diff --git a/src/V2/ClientOptions/BaseParameters.php b/src/V2/ClientOptions/BaseParameters.php index 3bc5a222..c682998b 100644 --- a/src/V2/ClientOptions/BaseParameters.php +++ b/src/V2/ClientOptions/BaseParameters.php @@ -29,11 +29,6 @@ abstract class BaseParameters */ public PollingOptions $pollingOptions; - /** - * @var string Slug of the product. - */ - public static string $slug; - /** * @param string $modelId ID of the model. * @param string|null $alias Optional file alias. From af7b6376018a7874a97683749cdfec23b9e642ef Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 11:27:37 +0100 Subject: [PATCH 05/17] add tests --- .github/workflows/_test-code-samples.yml | 33 --------- .github/workflows/_test-integrations.yml | 65 +++++++++-------- .github/workflows/_test-smoke.yml | 48 +++++++++++++ .github/workflows/_test-units.yml | 1 + .github/workflows/cron.yml | 2 +- .github/workflows/pull-request.yml | 2 +- docs/code_samples/v2_classification.txt | 37 ++++++++++ docs/code_samples/v2_crop.txt | 37 ++++++++++ ...fault_v2.txt => v2_extraction_polling.txt} | 0 docs/code_samples/v2_extraction_webhook.txt | 45 ++++++++++++ docs/code_samples/v2_ocr.txt | 37 ++++++++++ docs/code_samples/v2_split.txt | 37 ++++++++++ .../{OcrParams.php => OcrParameters.php} | 2 +- tests/V2/Product/CropFunctional.php | 60 ++++++++++++++++ tests/V2/Product/OcrFunctional.php | 55 ++++++++++++++ tests/V2/Product/SplitFunctional.php | 56 +++++++++++++++ ...ode_samples.sh => test_v1_code_samples.sh} | 21 +----- tests/test_v2_code_samples.sh | 71 +++++++++++++++++++ 18 files changed, 520 insertions(+), 89 deletions(-) delete mode 100644 .github/workflows/_test-code-samples.yml create mode 100644 .github/workflows/_test-smoke.yml create mode 100644 docs/code_samples/v2_classification.txt create mode 100644 docs/code_samples/v2_crop.txt rename docs/code_samples/{default_v2.txt => v2_extraction_polling.txt} (100%) create mode 100644 docs/code_samples/v2_extraction_webhook.txt create mode 100644 docs/code_samples/v2_ocr.txt create mode 100644 docs/code_samples/v2_split.txt rename src/V2/Product/Ocr/Params/{OcrParams.php => OcrParameters.php} (95%) create mode 100644 tests/V2/Product/CropFunctional.php create mode 100644 tests/V2/Product/OcrFunctional.php create mode 100644 tests/V2/Product/SplitFunctional.php rename tests/{test_code_samples.sh => test_v1_code_samples.sh} (77%) create mode 100755 tests/test_v2_code_samples.sh diff --git a/.github/workflows/_test-code-samples.yml b/.github/workflows/_test-code-samples.yml deleted file mode 100644 index 62a04106..00000000 --- a/.github/workflows/_test-code-samples.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Test Code Samples - -on: - workflow_call: - workflow_dispatch: - -jobs: - test: - name: Run Code Samples - timeout-minutes: 30 - strategy: - max-parallel: 2 - matrix: - php-version: - - "8.1" - - "8.4" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Set up Php ${{ matrix.php-version }} - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - - uses: ramsey/composer-install@v2 - - - name: Tests code samples - env: - MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} - run: | - ./tests/test_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} ${{ secrets.MINDEE_API_KEY_SE_TESTS }} ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 91be5107..3ac6cffb 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -29,7 +29,7 @@ jobs: matrix: php-version: - 8.1 - - 8.4 + - 8.5 runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v4 @@ -55,37 +55,36 @@ jobs: run: | ./vendor/bin/phpunit -c tests/functional.xml -# MacOS testing disabled because of capricious GS/ImageMagick/Homebrew behaviors -# integration-tests-macos: -# name: Run Integration Tests on MacOS -# timeout-minutes: 30 -# strategy: -# max-parallel: 2 -# matrix: -# php-version: -# - 8.1 -# - 8.4 -# runs-on: "macos-latest" -# steps: -# - uses: actions/checkout@v4 -# with: -# submodules: recursive -# - name: Set up php ${{ matrix.php-version }} -# uses: shivammathur/setup-php@v2 -# env: -# phpts: zts -# with: -# php-version: ${{ matrix.php-version }} -# extensions: curl, fileinfo, json, imagick -# - name: Install ImageMagick -# run: | -# brew update -# brew install imagemagick -# - uses: ramsey/composer-install@v2 -# - name: Unit testing with phpunit -# run: | -# ./vendor/bin/phpunit -c tests/functional.xml -# + integration-tests-macos: + name: Run Integration Tests on MacOS + timeout-minutes: 30 + strategy: + max-parallel: 2 + matrix: + php-version: + - 8.1 + - 8.5 + runs-on: "macos-latest" + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Set up php ${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + env: + phpts: zts + with: + php-version: ${{ matrix.php-version }} + extensions: curl, fileinfo, json, imagick + - name: Install ImageMagick + run: | + brew update + brew install imagemagick + - uses: ramsey/composer-install@v2 + - name: Unit testing with phpunit + run: | + ./vendor/bin/phpunit -c tests/functional.xml + integration-tests-windows: name: Run Integration Tests on Windows @@ -95,7 +94,7 @@ jobs: matrix: php-version: - 8.1 - - 8.4 + - 8.5 runs-on: "windows-latest" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/_test-smoke.yml b/.github/workflows/_test-smoke.yml new file mode 100644 index 00000000..49db9c61 --- /dev/null +++ b/.github/workflows/_test-smoke.yml @@ -0,0 +1,48 @@ +name: Smoke tests + +on: + workflow_call: + workflow_dispatch: + +env: + MINDEE_ACCOUNT_SE_TESTS: ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} + MINDEE_ENDPOINT_SE_TESTS: ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} + MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} + WORKFLOW_ID: ${{ secrets.WORKFLOW_ID_SE_TESTS }} + MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} + MINDEE_V2_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} + MINDEE_V2_SE_TESTS_BLANK_PDF_URL: ${{ secrets.MINDEE_V2_SE_TESTS_BLANK_PDF_URL }} + MINDEE_V2_CLASSIFICATION_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID }} + MINDEE_V2_CROP_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CROP_MODEL_ID }} + MINDEE_V2_OCR_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_OCR_MODEL_ID }} + MINDEE_V2_SPLIT_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_SPLIT_MODEL_ID }} + +jobs: + test: + name: Run Code Samples + timeout-minutes: 30 + strategy: + max-parallel: 2 + matrix: + php-version: + - "8.1" + - "8.5" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Php ${{ matrix.php-version }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + - uses: ramsey/composer-install@v2 + + - name: Tests V1 code samples + run: | + ./tests/test_v1_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} + + - name: Tests V2 code samples + run: | + ./tests/test_v2_code_samples.sh diff --git a/.github/workflows/_test-units.yml b/.github/workflows/_test-units.yml index 32d2f52d..befbc73e 100644 --- a/.github/workflows/_test-units.yml +++ b/.github/workflows/_test-units.yml @@ -14,6 +14,7 @@ jobs: - 8.2 - 8.3 - 8.4 + - 8.5 runs-on: "ubuntu-latest" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index ce38faf7..eec42819 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -6,5 +6,5 @@ on: jobs: test-code-samples: - uses: mindee/mindee-api-php/.github/workflows/_test-code-samples.yml@main + uses: mindee/mindee-api-php/.github/workflows/_test-smoke.yml@main secrets: inherit diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 72e8e32b..afad57dc 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -22,6 +22,6 @@ jobs: needs: test-units secrets: inherit test-code-samples: - uses: ./.github/workflows/_test-code-samples.yml + uses: ./.github/workflows/_test-smoke.yml needs: test-units secrets: inherit diff --git a/docs/code_samples/v2_classification.txt b/docs/code_samples/v2_classification.txt new file mode 100644 index 00000000..b576b432 --- /dev/null +++ b/docs/code_samples/v2_classification.txt @@ -0,0 +1,37 @@ +enqueueAndGetResult( + ClassificationResponse::class, + $inputSource, + $classificationParams +); + +// Print a summary of the response +echo strval($response->inference); + + +// Access the classification results +$classification = $response->inference->result->classification; diff --git a/docs/code_samples/v2_crop.txt b/docs/code_samples/v2_crop.txt new file mode 100644 index 00000000..056e5093 --- /dev/null +++ b/docs/code_samples/v2_crop.txt @@ -0,0 +1,37 @@ +enqueueAndGetResult( + CropResponse::class, + $inputSource, + $cropParams +); + +// Print a summary of the response +echo strval($response->inference); + + +// Access the crop results +$crops = $response->inference->result->crops; diff --git a/docs/code_samples/default_v2.txt b/docs/code_samples/v2_extraction_polling.txt similarity index 100% rename from docs/code_samples/default_v2.txt rename to docs/code_samples/v2_extraction_polling.txt diff --git a/docs/code_samples/v2_extraction_webhook.txt b/docs/code_samples/v2_extraction_webhook.txt new file mode 100644 index 00000000..37ecf891 --- /dev/null +++ b/docs/code_samples/v2_extraction_webhook.txt @@ -0,0 +1,45 @@ +enqueueInference( + $inputSource, + $inferenceParams +); + +// Print the job ID +echo strval($response->job->id); + +// IMPORTANT: save a record of which job ID corresponds to which file. diff --git a/docs/code_samples/v2_ocr.txt b/docs/code_samples/v2_ocr.txt new file mode 100644 index 00000000..bffe461a --- /dev/null +++ b/docs/code_samples/v2_ocr.txt @@ -0,0 +1,37 @@ +enqueueAndGetResult( + OcrResponse::class, + $inputSource, + $ocrParams +); + +// Print a summary of the response +echo strval($response->inference); + + +// Access the ocr results +$pages = $response->inference->result->pages; diff --git a/docs/code_samples/v2_split.txt b/docs/code_samples/v2_split.txt new file mode 100644 index 00000000..cbb6f3c3 --- /dev/null +++ b/docs/code_samples/v2_split.txt @@ -0,0 +1,37 @@ +enqueueAndGetResult( + SplitResponse::class, + $inputSource, + $splitParams +); + +// Print a summary of the response +echo strval($response->inference); + + +// Access the split results +$splits = $response->inference->result->splits; diff --git a/src/V2/Product/Ocr/Params/OcrParams.php b/src/V2/Product/Ocr/Params/OcrParameters.php similarity index 95% rename from src/V2/Product/Ocr/Params/OcrParams.php rename to src/V2/Product/Ocr/Params/OcrParameters.php index 17edb563..b86dd24e 100644 --- a/src/V2/Product/Ocr/Params/OcrParams.php +++ b/src/V2/Product/Ocr/Params/OcrParameters.php @@ -8,7 +8,7 @@ /** * Parameters for an ocr utility inference. */ -class OcrParams extends BaseParameters +class OcrParameters extends BaseParameters { /** * @var string Slug of the endpoint. diff --git a/tests/V2/Product/CropFunctional.php b/tests/V2/Product/CropFunctional.php new file mode 100644 index 00000000..df5a9e82 --- /dev/null +++ b/tests/V2/Product/CropFunctional.php @@ -0,0 +1,60 @@ +client = new ClientV2($apiKey); + + $this->cropModelId = getenv('MINDEE_V2_CROP_MODEL_ID') ?: ''; + } + + /** + * Tests the success of the crop process using a default sample file. + * + * @return void + */ + public function testCropDefaultSampleMustSucceed(): void + { + $inputSource = new PathInput( + TestingUtilities::getV2ProductDir() . '/crop/default_sample.jpg' + ); + + $productParams = new CropParameters($this->cropModelId); + $response = $this->client->enqueueAndGetResult(CropResponse::class, $inputSource, $productParams); + + $this->assertNotNull($response); + $this->assertNotNull($response->inference); + + $file = $response->inference->file; + $this->assertNotNull($file); + $this->assertSame("default_sample.jpg", $file->name); + + $result = $response->inference->result; + $this->assertNotNull($result); + + $crops = $result->crops; + $this->assertNotNull($crops); + $this->assertCount(2, $crops); + + foreach ($crops as $crop) { + $this->assertNotNull($crop->objectType); + $this->assertNotNull($crop->location); + } + } +} diff --git a/tests/V2/Product/OcrFunctional.php b/tests/V2/Product/OcrFunctional.php new file mode 100644 index 00000000..9a7c5bb5 --- /dev/null +++ b/tests/V2/Product/OcrFunctional.php @@ -0,0 +1,55 @@ +client = new ClientV2($apiKey); + + $this->ocrModelId = getenv('MINDEE_V2_OCR_MODEL_ID') ?: ''; + } + + /** + * Tests the success of the OCR process using a default sample file. + * + * @return void + */ + public function testOcrDefaultSampleMustSucceed(): void + { + $inputSource = new PathInput( + TestingUtilities::getV2ProductDir() . '/ocr/default_sample.jpg' + ); + + $productParams = new OcrParameters($this->ocrModelId); + $response = $this->client->enqueueAndGetResult(OcrResponse::class, $inputSource, $productParams); + + $this->assertNotNull($response); + $this->assertNotNull($response->inference); + + $file = $response->inference->file; + $this->assertNotNull($file); + $this->assertSame("default_sample.jpg", $file->name); + + $result = $response->inference->result; + $this->assertNotNull($result); + + $pages = $result->pages; + $this->assertNotNull($pages); + $this->assertCount(1, $pages); + } +} \ No newline at end of file diff --git a/tests/V2/Product/SplitFunctional.php b/tests/V2/Product/SplitFunctional.php new file mode 100644 index 00000000..d5276feb --- /dev/null +++ b/tests/V2/Product/SplitFunctional.php @@ -0,0 +1,56 @@ +client = new ClientV2($apiKey); + + $this->splitModelId = getenv('MINDEE_V2_SPLIT_MODEL_ID') ?: ''; + } + + /** + * Tests the success of the split process using a default sample file. + * + * @return void + */ + public function testSplitDefaultSampleMustSucceed(): void + { + // Matched exactly to the C# Constants.V2RootDir pathing + $inputSource = new PathInput( + TestingUtilities::getV2RootDir() . '/products/split/default_sample.pdf' + ); + + $productParams = new SplitParameters($this->splitModelId); + $response = $this->client->enqueueAndGetResult(SplitResponse::class, $inputSource, $productParams); + + $this->assertNotNull($response); + $this->assertNotNull($response->inference); + + $file = $response->inference->file; + $this->assertNotNull($file); + $this->assertSame("default_sample.pdf", $file->name); + + $result = $response->inference->result; + $this->assertNotNull($result); + + $splits = $result->splits; + $this->assertNotNull($splits); + $this->assertCount(2, $splits); + } +} diff --git a/tests/test_code_samples.sh b/tests/test_v1_code_samples.sh similarity index 77% rename from tests/test_code_samples.sh rename to tests/test_v1_code_samples.sh index d000d605..1ebc5fe2 100755 --- a/tests/test_code_samples.sh +++ b/tests/test_v1_code_samples.sh @@ -4,9 +4,6 @@ set -e OUTPUT_FILE='../test_code_samples/_test.php' ACCOUNT=$1 ENDPOINT=$2 -API_KEY=$3 -API_KEY_V2=$4 -MODEL_ID=$5 rm -fr ../test_code_samples mkdir ../test_code_samples @@ -34,15 +31,8 @@ composer dump-autoload chmod -R 777 ./ cd - -for f in $(find docs/code_samples -maxdepth 1 -name "*.txt" -not -name "workflow_*.txt" | sort -h) +for f in $(find docs/code_samples -maxdepth 1 -name "*.txt" -not -name "workflow_*.txt" -not -name "v2_*.txt" | sort -h) do - if echo "${f}" | grep -q "default_v2.txt"; then - if [ -z "${API_KEY_V2}" ] || [ -z "${MODEL_ID}" ]; then - echo "Skipping ${f} (API_KEY_V2 or MODEL_ID not supplied)" - echo - continue - fi - fi echo echo "###############################################" echo "${f}" @@ -52,7 +42,6 @@ do sed "s/my-api-key/$API_KEY/" "${f}" > $OUTPUT_FILE sed -i "s/\/path\/to\/the\/file.ext/..\/mindee-api-php\/tests\/resources\/file_types\/pdf\/blank_1.pdf/" $OUTPUT_FILE - # Only keeping the sample for display in the UI if echo "$f" | grep -q "custom_v1.txt" then continue @@ -72,14 +61,6 @@ do sed -i "s/my-version/1/" $OUTPUT_FILE fi - if echo "${f}" | grep -q "default_v2.txt" - then - sed -i "s/MY_API_KEY/$API_KEY_V2/" $OUTPUT_FILE - sed -i "s/MY_MODEL_ID/$MODEL_ID/" $OUTPUT_FILE - else - sed -i "s/my-api-key/$API_KEY/" $OUTPUT_FILE - fi - sleep 0.6 # avoid too many request errors php -d auto_prepend_file=vendor/autoload.php $OUTPUT_FILE done diff --git a/tests/test_v2_code_samples.sh b/tests/test_v2_code_samples.sh new file mode 100755 index 00000000..345f8617 --- /dev/null +++ b/tests/test_v2_code_samples.sh @@ -0,0 +1,71 @@ +#!/bin/sh +set -e + +OUTPUT_FILE='../test_code_samples/_test.php' + +rm -fr ../test_code_samples +mkdir ../test_code_samples + +cd ../test_code_samples + +EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" +php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" + +if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] +then + >&2 echo 'ERROR: Invalid installer checksum' + rm composer-setup.php + exit 1 +fi + +php composer-setup.php --quiet +rm composer-setup.php +composer init --no-interaction --name "mindee/test_code_samples" --description "testing package" --author "mindee" --type "project" +composer install +composer config repositories.mindee/mindee '{"type": "path", "url":"../mindee-api-php"}' +composer require mindee/mindee @dev +composer dump-autoload +chmod -R 777 ./ +cd - + +for f in $(find docs/code_samples -maxdepth 1 -name "v2_*.txt" | sort -h) +do + echo "###############################################" + echo "${f}" + echo "###############################################" + echo + + sed "s/MY_API_KEY/$MINDEE_V2_API_KEY/" "${f}" > $OUTPUT_FILE + sed -i "s/\/path\/to\/the\/file.ext/tests\/resources\/file_types\/pdf\/blank_1.pdf/" $OUTPUT_FILE + + if echo "${f}" | grep -q "v2_classification" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_CLASSIFICATION_MODEL_ID}/" $OUTPUT_FILE + fi + + if echo "${f}" | grep -q "v2_crop" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_CROP_MODEL_ID}/" $OUTPUT_FILE + fi + + if echo "${f}" | grep -q "v2_extraction" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_FINDOC_MODEL_ID}/" $OUTPUT_FILE + sed -i "s/MY_WEBHOOK_ID/${MINDEE_V2_FAILURE_WEBHOOK_ID}/" $OUTPUT_FILE + fi + + if echo "${f}" | grep -q "v2_ocr" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_OCR_MODEL_ID}/" $OUTPUT_FILE + fi + + if echo "${f}" | grep -q "v2_split" + then + sed -i "s/MY_MODEL_ID/${MINDEE_V2_SPLIT_MODEL_ID}/" $OUTPUT_FILE + fi + + echo + sleep 0.6 # avoid too many request errors + php -d auto_prepend_file=vendor/autoload.php $OUTPUT_FILE +done From 359c973ec6efe36f63997a02a134306ca7bffd2e Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 11:38:12 +0100 Subject: [PATCH 06/17] fix tests --- .github/workflows/_test-smoke.yml | 2 +- .github/workflows/cron.yml | 2 +- .github/workflows/pull-request.yml | 2 +- src/Http/MindeeApiV2.php | 2 +- tests/V2/Product/OcrFunctional.php | 2 +- tests/V2/Product/SplitFunctional.php | 3 +-- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/_test-smoke.yml b/.github/workflows/_test-smoke.yml index 49db9c61..aa51a6df 100644 --- a/.github/workflows/_test-smoke.yml +++ b/.github/workflows/_test-smoke.yml @@ -19,7 +19,7 @@ env: jobs: test: - name: Run Code Samples + name: Run Smoke Tests timeout-minutes: 30 strategy: max-parallel: 2 diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index eec42819..a306b37c 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -5,6 +5,6 @@ on: - cron: '42 0 * * *' jobs: - test-code-samples: + smoke-test: uses: mindee/mindee-api-php/.github/workflows/_test-smoke.yml@main secrets: inherit diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index afad57dc..0fdf33b6 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -21,7 +21,7 @@ jobs: uses: ./.github/workflows/_test-integrations.yml needs: test-units secrets: inherit - test-code-samples: + smoke-test: uses: ./.github/workflows/_test-smoke.yml needs: test-units secrets: inherit diff --git a/src/Http/MindeeApiV2.php b/src/Http/MindeeApiV2.php index 67f0e804..3927a394 100644 --- a/src/Http/MindeeApiV2.php +++ b/src/Http/MindeeApiV2.php @@ -401,7 +401,7 @@ private function documentEnqueuePost( } if (isset($params->webhooksIds) && count($params->webhooksIds) > 0) { if (PHP_VERSION_ID < 80200 && count($params->webhooksIds) > 1) { - # NOTE: see https://bugs.php.net/bug.php?id=51634 + // NOTE: see https://bugs.php.net/bug.php?id=51634 error_log("PHP version is too low to support webbook array destructuring. \nOnly the first webhook ID will be sent to the server."); $postFields['webhook_ids'] = $params->webhooksIds[0]; diff --git a/tests/V2/Product/OcrFunctional.php b/tests/V2/Product/OcrFunctional.php index 9a7c5bb5..70d1eb1d 100644 --- a/tests/V2/Product/OcrFunctional.php +++ b/tests/V2/Product/OcrFunctional.php @@ -52,4 +52,4 @@ public function testOcrDefaultSampleMustSucceed(): void $this->assertNotNull($pages); $this->assertCount(1, $pages); } -} \ No newline at end of file +} diff --git a/tests/V2/Product/SplitFunctional.php b/tests/V2/Product/SplitFunctional.php index d5276feb..443a30aa 100644 --- a/tests/V2/Product/SplitFunctional.php +++ b/tests/V2/Product/SplitFunctional.php @@ -31,9 +31,8 @@ protected function setUp(): void */ public function testSplitDefaultSampleMustSucceed(): void { - // Matched exactly to the C# Constants.V2RootDir pathing $inputSource = new PathInput( - TestingUtilities::getV2RootDir() . '/products/split/default_sample.pdf' + TestingUtilities::getV2ProductDir() . '/products/split/default_sample.pdf' ); $productParams = new SplitParameters($this->splitModelId); From 7a8de4db444e037b2492c7d694f82aa1542a82de Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 11:55:33 +0100 Subject: [PATCH 07/17] disable macos tests + fix test --- .github/workflows/_test-integrations.yml | 61 ++++++++++++------------ tests/V2/Product/SplitFunctional.php | 2 +- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index 3ac6cffb..d62f1401 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -55,36 +55,37 @@ jobs: run: | ./vendor/bin/phpunit -c tests/functional.xml - integration-tests-macos: - name: Run Integration Tests on MacOS - timeout-minutes: 30 - strategy: - max-parallel: 2 - matrix: - php-version: - - 8.1 - - 8.5 - runs-on: "macos-latest" - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Set up php ${{ matrix.php-version }} - uses: shivammathur/setup-php@v2 - env: - phpts: zts - with: - php-version: ${{ matrix.php-version }} - extensions: curl, fileinfo, json, imagick - - name: Install ImageMagick - run: | - brew update - brew install imagemagick - - uses: ramsey/composer-install@v2 - - name: Unit testing with phpunit - run: | - ./vendor/bin/phpunit -c tests/functional.xml - +# MacOS testing disabled because of capricious GS/ImageMagick/Homebrew behaviors +# integration-tests-macos: +# name: Run Integration Tests on MacOS +# timeout-minutes: 30 +# strategy: +# max-parallel: 2 +# matrix: +# php-version: +# - 8.1 +# - 8.5 +# runs-on: "macos-latest" +# steps: +# - uses: actions/checkout@v4 +# with: +# submodules: recursive +# - name: Set up php ${{ matrix.php-version }} +# uses: shivammathur/setup-php@v2 +# env: +# phpts: zts +# with: +# php-version: ${{ matrix.php-version }} +# extensions: curl, fileinfo, json, imagick +# - name: Install ImageMagick +# run: | +# brew update +# brew install imagemagick +# - uses: ramsey/composer-install@v2 +# - name: Unit testing with phpunit +# run: | +# ./vendor/bin/phpunit -c tests/functional.xml +# integration-tests-windows: name: Run Integration Tests on Windows diff --git a/tests/V2/Product/SplitFunctional.php b/tests/V2/Product/SplitFunctional.php index 443a30aa..92d2cbc8 100644 --- a/tests/V2/Product/SplitFunctional.php +++ b/tests/V2/Product/SplitFunctional.php @@ -32,7 +32,7 @@ protected function setUp(): void public function testSplitDefaultSampleMustSucceed(): void { $inputSource = new PathInput( - TestingUtilities::getV2ProductDir() . '/products/split/default_sample.pdf' + TestingUtilities::getV2ProductDir() . '/split/default_sample.pdf' ); $productParams = new SplitParameters($this->splitModelId); From 565e97bf51361c89c161d4eac87acac8bdcffd8e Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:50:37 +0100 Subject: [PATCH 08/17] add webhook id test --- .github/workflows/_test-integrations.yml | 1 + docs/code_samples/v2_extraction_webhook.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/_test-integrations.yml b/.github/workflows/_test-integrations.yml index d62f1401..3273777f 100644 --- a/.github/workflows/_test-integrations.yml +++ b/.github/workflows/_test-integrations.yml @@ -13,6 +13,7 @@ env: MINDEE_API_KEY: ${{ secrets.MINDEE_API_KEY_SE_TESTS }} WORKFLOW_ID: ${{ secrets.WORKFLOW_ID_SE_TESTS }} MINDEE_V2_API_KEY: ${{ secrets.MINDEE_V2_SE_TESTS_API_KEY }} + MINDEE_V2_FAILURE_WEBHOOK_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FAILURE_WEBHOOK_ID }} MINDEE_V2_FINDOC_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_FINDOC_MODEL_ID }} MINDEE_V2_SE_TESTS_BLANK_PDF_URL: ${{ secrets.MINDEE_V2_SE_TESTS_BLANK_PDF_URL }} MINDEE_V2_CLASSIFICATION_MODEL_ID: ${{ secrets.MINDEE_V2_SE_TESTS_CLASSIFICATION_MODEL_ID }} diff --git a/docs/code_samples/v2_extraction_webhook.txt b/docs/code_samples/v2_extraction_webhook.txt index 37ecf891..3b439da0 100644 --- a/docs/code_samples/v2_extraction_webhook.txt +++ b/docs/code_samples/v2_extraction_webhook.txt @@ -16,6 +16,7 @@ $mindeeClient = new ClientV2($apiKey); $inferenceParams = new InferenceParameters( // ID of the model, required. $modelId, + webhookIds: ["MY_WEBHOOK_ID"], // Options: set to `true` or `false` to override defaults From ba2826c8b58512767a3c191f4b101b485c85b223 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:44:16 +0100 Subject: [PATCH 09/17] fix slug --- src/Parsing/V2/InferenceResponse.php | 5 +++++ src/V2/Product/Classification/ClassificationResponse.php | 5 +++++ src/V2/Product/Crop/CropResponse.php | 5 +++++ src/V2/Product/Ocr/OcrResponse.php | 5 +++++ src/V2/Product/Split/SplitResponse.php | 5 +++++ 5 files changed, 25 insertions(+) diff --git a/src/Parsing/V2/InferenceResponse.php b/src/Parsing/V2/InferenceResponse.php index fc86b2e9..cbb3ea87 100644 --- a/src/Parsing/V2/InferenceResponse.php +++ b/src/Parsing/V2/InferenceResponse.php @@ -12,6 +12,11 @@ class InferenceResponse extends BaseResponse */ public Inference $inference; + /** + * @var string Slug for the inference. + */ + public static string $slug = "extraction"; + /** * @param array $rawResponse Raw server response array. */ diff --git a/src/V2/Product/Classification/ClassificationResponse.php b/src/V2/Product/Classification/ClassificationResponse.php index db684191..95758c07 100644 --- a/src/V2/Product/Classification/ClassificationResponse.php +++ b/src/V2/Product/Classification/ClassificationResponse.php @@ -14,6 +14,11 @@ class ClassificationResponse extends BaseResponse */ public ClassificationInference $inference; + /** + * @var string Slug for the inference. + */ + public static string $slug = "classification"; + /** * @param array $rawResponse Raw server response array. */ diff --git a/src/V2/Product/Crop/CropResponse.php b/src/V2/Product/Crop/CropResponse.php index 033cd1c9..cfd07e29 100644 --- a/src/V2/Product/Crop/CropResponse.php +++ b/src/V2/Product/Crop/CropResponse.php @@ -14,6 +14,11 @@ class CropResponse extends BaseResponse */ public CropInference $inference; + /** + * @var string Slug for the inference. + */ + public static string $slug = "crop"; + /** * @param array $rawResponse Raw server response array. */ diff --git a/src/V2/Product/Ocr/OcrResponse.php b/src/V2/Product/Ocr/OcrResponse.php index fc657c05..1861964e 100644 --- a/src/V2/Product/Ocr/OcrResponse.php +++ b/src/V2/Product/Ocr/OcrResponse.php @@ -14,6 +14,11 @@ class OcrResponse extends BaseResponse */ public OcrInference $inference; + /** + * @var string Slug for the inference. + */ + public static string $slug = "ocr"; + /** * @param array $rawResponse Raw server response array. */ diff --git a/src/V2/Product/Split/SplitResponse.php b/src/V2/Product/Split/SplitResponse.php index f5c00076..d012af8a 100644 --- a/src/V2/Product/Split/SplitResponse.php +++ b/src/V2/Product/Split/SplitResponse.php @@ -14,6 +14,11 @@ class SplitResponse extends BaseResponse */ public SplitInference $inference; + /** + * @var string Slug for the inference. + */ + public static string $slug = "split"; + /** * @param array $rawResponse Raw server response array. */ From 9d99a370c780da6a3c6cba2e90161126b6c84217 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:45:11 +0100 Subject: [PATCH 10/17] fix typo --- docs/code_samples/v2_extraction_webhook.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/code_samples/v2_extraction_webhook.txt b/docs/code_samples/v2_extraction_webhook.txt index 3b439da0..d5cf9509 100644 --- a/docs/code_samples/v2_extraction_webhook.txt +++ b/docs/code_samples/v2_extraction_webhook.txt @@ -16,7 +16,7 @@ $mindeeClient = new ClientV2($apiKey); $inferenceParams = new InferenceParameters( // ID of the model, required. $modelId, - webhookIds: ["MY_WEBHOOK_ID"], + webhooksIds: ["MY_WEBHOOK_ID"], // Options: set to `true` or `false` to override defaults From e0464437b7039ce9ee53a8251a4177168adb0d0c Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:58:13 +0100 Subject: [PATCH 11/17] fix props accessing --- src/Http/MindeeApiV2.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Http/MindeeApiV2.php b/src/Http/MindeeApiV2.php index 3927a394..90483f7e 100644 --- a/src/Http/MindeeApiV2.php +++ b/src/Http/MindeeApiV2.php @@ -284,7 +284,7 @@ public function reqGetResult( } try { - $slug = new ReflectionProperty($responseClass, 'slug'); + $slugProperty = new ReflectionProperty($responseClass, 'slug'); } catch (ReflectionException $e) { throw new MindeeApiException( "Unable to access slug property of " . $responseClass, @@ -292,7 +292,8 @@ public function reqGetResult( $e ); } - $response = $this->sendGetRequest($this->baseUrl . "/products/$resultId/results/$slug"); + $url = $this->baseUrl . "/products/{$slugProperty->getValue()}/results/$resultId"; + $response = $this->sendGetRequest($url); return $this->processResponse($responseClass, $response); } From 7cd043596d61c86d547e6c68f2548bbe39aa26f5 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Wed, 18 Mar 2026 15:16:13 +0100 Subject: [PATCH 12/17] fix webhooks --- .github/workflows/_test-smoke.yml | 4 ++-- src/Http/MindeeApiV2.php | 5 +++-- tests/{test_v1_code_samples.sh => test_code_samples_v1.sh} | 0 tests/{test_v2_code_samples.sh => test_code_samples_v2.sh} | 0 4 files changed, 5 insertions(+), 4 deletions(-) rename tests/{test_v1_code_samples.sh => test_code_samples_v1.sh} (100%) rename tests/{test_v2_code_samples.sh => test_code_samples_v2.sh} (100%) diff --git a/.github/workflows/_test-smoke.yml b/.github/workflows/_test-smoke.yml index aa51a6df..bbdb0026 100644 --- a/.github/workflows/_test-smoke.yml +++ b/.github/workflows/_test-smoke.yml @@ -41,8 +41,8 @@ jobs: - name: Tests V1 code samples run: | - ./tests/test_v1_code_samples.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} + ./tests/test_code_samples_v1.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} - name: Tests V2 code samples run: | - ./tests/test_v2_code_samples.sh + ./tests/test_code_samples_v2.sh diff --git a/src/Http/MindeeApiV2.php b/src/Http/MindeeApiV2.php index 90483f7e..3c043926 100644 --- a/src/Http/MindeeApiV2.php +++ b/src/Http/MindeeApiV2.php @@ -407,7 +407,9 @@ private function documentEnqueuePost( \nOnly the first webhook ID will be sent to the server."); $postFields['webhook_ids'] = $params->webhooksIds[0]; } else { - $postFields['webhook_ids'] = $params->webhooksIds; + foreach ($params->webhooksIds as $webhookId) { + $postFields['webhook_ids[]'] = $webhookId; + } } } if (isset($params->alias)) { @@ -424,7 +426,6 @@ private function documentEnqueuePost( if (!empty($curlError)) { throw new MindeeException("cURL error:\n$curlError"); } - curl_close($ch); return $resp; diff --git a/tests/test_v1_code_samples.sh b/tests/test_code_samples_v1.sh similarity index 100% rename from tests/test_v1_code_samples.sh rename to tests/test_code_samples_v1.sh diff --git a/tests/test_v2_code_samples.sh b/tests/test_code_samples_v2.sh similarity index 100% rename from tests/test_v2_code_samples.sh rename to tests/test_code_samples_v2.sh From 5348a9b2336e077b73b25f3fcfd0959fab848621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Thu, 19 Mar 2026 10:16:20 +0100 Subject: [PATCH 13/17] update code samples --- .github/workflows/_test-smoke.yml | 8 ++++---- docs/code_samples/v2_classification.txt | 1 - docs/code_samples/v2_crop.txt | 1 - docs/code_samples/v2_extraction_polling.txt | 3 +++ docs/code_samples/v2_extraction_webhook.txt | 2 +- docs/code_samples/v2_ocr.txt | 1 - docs/code_samples/v2_split.txt | 1 - 7 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/_test-smoke.yml b/.github/workflows/_test-smoke.yml index bbdb0026..4fe960f6 100644 --- a/.github/workflows/_test-smoke.yml +++ b/.github/workflows/_test-smoke.yml @@ -39,10 +39,10 @@ jobs: php-version: ${{ matrix.php-version }} - uses: ramsey/composer-install@v2 - - name: Tests V1 code samples - run: | - ./tests/test_code_samples_v1.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} - - name: Tests V2 code samples run: | ./tests/test_code_samples_v2.sh + + - name: Tests V1 code samples + run: | + ./tests/test_code_samples_v1.sh ${{ secrets.MINDEE_ACCOUNT_SE_TESTS }} ${{ secrets.MINDEE_ENDPOINT_SE_TESTS }} diff --git a/docs/code_samples/v2_classification.txt b/docs/code_samples/v2_classification.txt index b576b432..dffea452 100644 --- a/docs/code_samples/v2_classification.txt +++ b/docs/code_samples/v2_classification.txt @@ -32,6 +32,5 @@ $response = $mindeeClient->enqueueAndGetResult( // Print a summary of the response echo strval($response->inference); - // Access the classification results $classification = $response->inference->result->classification; diff --git a/docs/code_samples/v2_crop.txt b/docs/code_samples/v2_crop.txt index 056e5093..2d050c32 100644 --- a/docs/code_samples/v2_crop.txt +++ b/docs/code_samples/v2_crop.txt @@ -32,6 +32,5 @@ $response = $mindeeClient->enqueueAndGetResult( // Print a summary of the response echo strval($response->inference); - // Access the crop results $crops = $response->inference->result->crops; diff --git a/docs/code_samples/v2_extraction_polling.txt b/docs/code_samples/v2_extraction_polling.txt index 96283302..0723f7d7 100644 --- a/docs/code_samples/v2_extraction_polling.txt +++ b/docs/code_samples/v2_extraction_polling.txt @@ -41,3 +41,6 @@ $response = $mindeeClient->enqueueAndGetInference( // Print a summary of the response echo strval($response->inference); + +// Access the extracted fields +$fields = response->inference->result->fields; diff --git a/docs/code_samples/v2_extraction_webhook.txt b/docs/code_samples/v2_extraction_webhook.txt index d5cf9509..15496836 100644 --- a/docs/code_samples/v2_extraction_webhook.txt +++ b/docs/code_samples/v2_extraction_webhook.txt @@ -35,7 +35,7 @@ $inferenceParams = new InferenceParameters( $inputSource = new PathInput($filePath); // Upload the file -$response = $mindeeClient->enqueueInference( +$response = $mindeeClient->enqueue( $inputSource, $inferenceParams ); diff --git a/docs/code_samples/v2_ocr.txt b/docs/code_samples/v2_ocr.txt index bffe461a..22af46b8 100644 --- a/docs/code_samples/v2_ocr.txt +++ b/docs/code_samples/v2_ocr.txt @@ -32,6 +32,5 @@ $response = $mindeeClient->enqueueAndGetResult( // Print a summary of the response echo strval($response->inference); - // Access the ocr results $pages = $response->inference->result->pages; diff --git a/docs/code_samples/v2_split.txt b/docs/code_samples/v2_split.txt index cbb6f3c3..92f3f56e 100644 --- a/docs/code_samples/v2_split.txt +++ b/docs/code_samples/v2_split.txt @@ -32,6 +32,5 @@ $response = $mindeeClient->enqueueAndGetResult( // Print a summary of the response echo strval($response->inference); - // Access the split results $splits = $response->inference->result->splits; From 1d324492fa08549c378969550cbec881a33a95d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ianar=C3=A9=20S=C3=A9vi?= Date: Thu, 19 Mar 2026 10:23:19 +0100 Subject: [PATCH 14/17] job id --- src/ClientV2.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ClientV2.php b/src/ClientV2.php index 6eda3b43..ee16f880 100644 --- a/src/ClientV2.php +++ b/src/ClientV2.php @@ -145,12 +145,12 @@ public function enqueueAndGetResult( throw new MindeeException("Enqueueing of the document failed."); } - $queueId = $enqueueResponse->job->id; - error_log("Successfully enqueued document with job id: " . $queueId); + $jobId = $enqueueResponse->job->id; + error_log("Successfully enqueued document with job ID: " . $jobId); $this->customSleep($pollingOptions->initialDelaySec); $retryCounter = 1; - $pollResults = $this->getJob($queueId); + $pollResults = $this->getJob($jobId); while ($retryCounter < $pollingOptions->maxRetries) { if ($pollResults->job->status === "Failed") { @@ -161,13 +161,13 @@ public function enqueueAndGetResult( } error_log( - "Polling server for parsing result with queueId: " . $queueId . + "Polling server for parsing result with job ID: " . $jobId . ". Attempt number " . $retryCounter . " of " . $pollingOptions->maxRetries . ". Job status: " . $pollResults->job->status ); $this->customSleep($pollingOptions->delaySec); - $pollResults = $this->getJob($queueId); + $pollResults = $this->getJob($jobId); $retryCounter++; } From f5d296ced8e5c7d77527b4db3f032d476fd69cd9 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:47:41 +0100 Subject: [PATCH 15/17] fixes --- src/ClientV2.php | 20 +++++++++++-- src/Http/MindeeApiV2.php | 39 ++----------------------- src/Input/InferenceParameters.php | 27 +++++++++++++++++ src/V2/ClientOptions/BaseParameters.php | 25 ++++++++++++++++ 4 files changed, 71 insertions(+), 40 deletions(-) diff --git a/src/ClientV2.php b/src/ClientV2.php index ee16f880..fc0ab147 100644 --- a/src/ClientV2.php +++ b/src/ClientV2.php @@ -80,14 +80,28 @@ public function getInference(string $inferenceId): InferenceResponse * @template T of BaseResponse * @param string $responseClass The response class to construct. * @phpstan-param class-string $responseClass - * @param string $jobUrl URL of the job. + * @param string $resultUrl URL of the result. * @return T A response containing parsing results. */ public function getResultFromUrl( string $responseClass, - string $jobUrl + string $resultUrl ): BaseResponse { - return $this->mindeeApi->reqGetResultFromUrl($responseClass, $jobUrl); + return $this->mindeeApi->reqGetResultFromUrl($responseClass, $resultUrl); + } + + /** + * @template T of BaseResponse + * @param string $responseClass The response class to construct. + * @phpstan-param class-string $responseClass + * @param string $resultId ID of the result. + * @return T A response containing parsing results. + */ + public function getResult( + string $responseClass, + string $resultId + ): BaseResponse { + return $this->mindeeApi->reqGetResult($responseClass, $resultId); } /** diff --git a/src/Http/MindeeApiV2.php b/src/Http/MindeeApiV2.php index 3c043926..38c9957f 100644 --- a/src/Http/MindeeApiV2.php +++ b/src/Http/MindeeApiV2.php @@ -372,49 +372,14 @@ private function documentEnqueuePost( ): array { /** @var CurlHandle $ch */ $ch = $this->initChannel(); - $postFields = ['model_id' => $params->modelId]; + $postFields = $params->asHash(); + if ($inputSource instanceof URLInputSource) { $postFields['url'] = $inputSource->url; } elseif ($inputSource instanceof LocalInputSource) { $inputSource->checkNeedsFix(); $postFields['file'] = $inputSource->fileObject; } - - if (is_a($params, InferenceParameters::class)) { - if (isset($params->rawText)) { - $postFields['raw_text'] = $params->rawText ? 'true' : 'false'; - } - if (isset($params->polygon)) { - $postFields['polygon'] = $params->polygon ? 'true' : 'false'; - } - if (isset($params->confidence)) { - $postFields['confidence'] = $params->confidence ? 'true' : 'false'; - } - if (isset($params->rag)) { - $postFields['rag'] = $params->rag ? 'true' : 'false'; - } - if (isset($params->textContext)) { - $postFields['text_context'] = $params->textContext; - } - if (isset($params->dataSchema)) { - $postFields['data_schema'] = strval($params->dataSchema); - } - } - if (isset($params->webhooksIds) && count($params->webhooksIds) > 0) { - if (PHP_VERSION_ID < 80200 && count($params->webhooksIds) > 1) { - // NOTE: see https://bugs.php.net/bug.php?id=51634 - error_log("PHP version is too low to support webbook array destructuring. - \nOnly the first webhook ID will be sent to the server."); - $postFields['webhook_ids'] = $params->webhooksIds[0]; - } else { - foreach ($params->webhooksIds as $webhookId) { - $postFields['webhook_ids[]'] = $webhookId; - } - } - } - if (isset($params->alias)) { - $postFields['alias'] = $params->alias; - } $url = $this->baseUrl . "/products/{$params::$slug}/enqueue"; curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields); diff --git a/src/Input/InferenceParameters.php b/src/Input/InferenceParameters.php index f1e79076..27ea32bd 100644 --- a/src/Input/InferenceParameters.php +++ b/src/Input/InferenceParameters.php @@ -88,4 +88,31 @@ public function __construct( $this->dataSchema = new DataSchema($dataSchema); } } + + /** + * @return array Hash representation. + */ + public function asHash(): array + { + $outHash = parent::asHash(); + if (isset($this->rag)) { + $outHash['rag'] = $this->rag; + } + if (isset($this->rawText)) { + $outHash['raw_text'] = $this->rawText; + } + if (isset($this->polygon)) { + $outHash['polygon'] = $this->polygon; + } + if (isset($this->confidence)) { + $outHash['confidence'] = $this->confidence; + } + if (isset($this->textContext)) { + $outHash['text_context'] = $this->textContext; + } + if (isset($this->dataSchema)) { + $outHash['data_schema'] = strval($this->dataSchema); + } + return $outHash; + } } diff --git a/src/V2/ClientOptions/BaseParameters.php b/src/V2/ClientOptions/BaseParameters.php index c682998b..4b4a8465 100644 --- a/src/V2/ClientOptions/BaseParameters.php +++ b/src/V2/ClientOptions/BaseParameters.php @@ -52,4 +52,29 @@ public function __construct(string $modelId, ?string $alias, ?array $webhooksIds } $this->pollingOptions = $pollingOptions; } + + /** + * @return array Hash representation. + */ + public function asHash(): array + { + $outHash = ['model_id' => $this->modelId]; + if (isset($this->alias)) { + $outHash['alias'] = $this->alias; + } + + if (isset($this->webhooksIds) && count($this->webhooksIds) > 0) { + if (PHP_VERSION_ID < 80200 && count($this->webhooksIds) > 1) { + // NOTE: see https://bugs.php.net/bug.php?id=51634 + error_log("PHP version is too low to support webbook array destructuring. + \nOnly the first webhook ID will be sent to the server."); + $outHash['webhook_ids'] = $this->webhooksIds[0]; + } else { + foreach ($this->webhooksIds as $webhookId) { + $outHash['webhook_ids[]'] = $webhookId; + } + } + } + return $outHash; + } } From 80f3d54f4a012def483f2606f10a1eaa7c061c85 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:12:02 +0100 Subject: [PATCH 16/17] fix params serialization --- src/Input/InferenceParameters.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Input/InferenceParameters.php b/src/Input/InferenceParameters.php index 27ea32bd..1cc3f51b 100644 --- a/src/Input/InferenceParameters.php +++ b/src/Input/InferenceParameters.php @@ -96,16 +96,16 @@ public function asHash(): array { $outHash = parent::asHash(); if (isset($this->rag)) { - $outHash['rag'] = $this->rag; + $outHash['rag'] = $this->rag ? 'true' : 'false'; } if (isset($this->rawText)) { - $outHash['raw_text'] = $this->rawText; + $outHash['raw_text'] = $this->rawText ? 'true' : 'false'; } if (isset($this->polygon)) { - $outHash['polygon'] = $this->polygon; + $outHash['polygon'] = $this->polygon ? 'true' : 'false'; } if (isset($this->confidence)) { - $outHash['confidence'] = $this->confidence; + $outHash['confidence'] = $this->confidence ? 'true' : 'false'; } if (isset($this->textContext)) { $outHash['text_context'] = $this->textContext; From d6ce41f150a3460cfd381249f5eb65a786ee0fd0 Mon Sep 17 00:00:00 2001 From: sebastianMindee <130448732+sebastianMindee@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:02:20 +0100 Subject: [PATCH 17/17] fix typo --- docs/code_samples/v2_extraction_polling.txt | 2 +- docs/code_samples/v2_extraction_webhook.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/code_samples/v2_extraction_polling.txt b/docs/code_samples/v2_extraction_polling.txt index 0723f7d7..047e07b7 100644 --- a/docs/code_samples/v2_extraction_polling.txt +++ b/docs/code_samples/v2_extraction_polling.txt @@ -43,4 +43,4 @@ $response = $mindeeClient->enqueueAndGetInference( echo strval($response->inference); // Access the extracted fields -$fields = response->inference->result->fields; +$fields = $response->inference->result->fields; diff --git a/docs/code_samples/v2_extraction_webhook.txt b/docs/code_samples/v2_extraction_webhook.txt index 15496836..c5046c56 100644 --- a/docs/code_samples/v2_extraction_webhook.txt +++ b/docs/code_samples/v2_extraction_webhook.txt @@ -41,6 +41,6 @@ $response = $mindeeClient->enqueue( ); // Print the job ID -echo strval($response->job->id); +echo $response->job->id; // IMPORTANT: save a record of which job ID corresponds to which file.