Skip to content

feat: vNext migration#11

Merged
AndreyHirsa merged 2 commits intomainfrom
feat/v-next
Mar 7, 2026
Merged

feat: vNext migration#11
AndreyHirsa merged 2 commits intomainfrom
feat/v-next

Conversation

@AndreyHirsa
Copy link
Contributor

@AndreyHirsa AndreyHirsa commented Mar 4, 2026

Summary

  • Migrated SDK from legacy API to vNext API
  • engineId is now an optional configuration parameter (moved from URL path to request body)
  • Consolidated to single /process/localize endpoint (replaces /process/{engineId}/localize)

Changes

src/LingoDotDevEngine.php

  • Removed required engineId validation — it is now optional
  • Localization endpoint: POST /process/localize (replaces POST /process/{engineId}/localize)
  • engineId is included in request body only when provided in config
  • Auth header: X-API-Key: {apiKey}
  • Request body uses camelCase keys (sourceLocale, targetLocale)
  • sessionId (generated once per engine instance) included in request body
  • Reference is only included in request body when explicitly provided

tests/LingoDotDevEngineTest.php

  • Removed testConstructorRequiresEngineId (no longer required)
  • Added testConstructorWithoutEngineId — verifies engine creation with only apiKey
  • Updated testLocalizeChunkUrlAndBody — URL assertion changed to /process/localize, added engineId body assertion
  • Added testLocalizeChunkUrlAndBodyWithoutEngineId — verifies body omits engineId when not configured

test-all-methods.php

  • engineId is now an optional CLI argument
  • Added apiUrl as optional third CLI argument
  • Updated usage: php test-all-methods.php <api_key> [engine_id] [api_url]

README.md

  • engineId marked as optional in configuration table
  • Updated all examples to show engineId as optional
  • Updated demo app to conditionally pass engineId

Test plan

  • All 20 unit tests pass
  • All 7 integration tests pass against live API
  • Works with engineId provided
  • Works without engineId

Summary by CodeRabbit

  • New Features

    • Added optional engineId configuration support
    • Introduced new configuration options table (apiKey, engineId, apiUrl, batchSize, idealBatchItemSize)
    • Added examples for object localization and chat localization
    • Added environment variable support (LINGODOTDEV_ENGINE_ID)
  • Documentation

    • Updated README with engineId usage and configuration details
    • Expanded examples demonstrating additional localization features
  • Chores

    • Removed unused dependency

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Updates to the PHP SDK and demos to support optional engineId, switch auth to X-API-Key, change API host to api.lingo.dev, introduce per-instance sessionId for requests, adapt endpoints to /process/* paths, remove the respect/validation dependency, and expand docs, demo scripts, and tests to cover new payload/headers and behaviors.

Changes

Cohort / File(s) Summary
Documentation & Examples
README.md
Adds engineId as an optional init/config option, adds a Configuration Options table, updates usage and demo snippets to show env-driven engineId and notes on optional usage.
Package Manifest
composer.json
Removes respect/validation from requires.
Core Engine Implementation
src/LingoDotDevEngine.php
Changes API base to api.lingo.dev; uses X-API-Key header (removes Bearer Authorization); introduces private sessionId stored on the engine instance; updates request payloads to include sessionId and top-level sourceLocale/targetLocale; removes workflowId parameter from _localizeChunk; endpoint paths updated to /process/localize and /process/recognize; validates source/target locale types; includes engineId in requests when provided.
CLI Demo / Test Harness
test-all-methods.php
Now accepts optional engine_id and api_url args; builds config array with conditional engineId/apiUrl; expands test cases and scenarios (text/object/chat/batch/recognize/progress), adds pass/fail counters and summary exit code, and improves JSON pretty-printing.
Unit Tests
tests/LingoDotDevEngineTest.php
Extensive test additions/refactors: mock engine creation with request history, tests for default/explicit apiUrl, X-API-Key header presence, localize chunk URL/body and sessionId consistency, reference inclusion/omission, recognize endpoint path, and constructor/apiKey/engineId behaviors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop through code with tiny feet,

EngineId tucked in, tidy and neat.
X-API-Key sings a bright new tune,
Sessions hum from morn to noon,
Tests applaud — a carrot treat! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: vNext migration' clearly and specifically summarizes the main objective of the changeset: migrating the SDK from the legacy API to the vNext API.
Docstring Coverage ✅ Passed Docstring coverage is 96.55% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/v-next

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/LingoDotDevEngineTest.php (1)

57-60: Consider extracting _httpClient injection into one helper.

The same reflection block is duplicated in two places, which makes future test-scaffold changes easier to miss.

♻️ Possible refactor
+    private function _injectHttpClient(LingoDotDevEngine $engine, Client $client): void
+    {
+        $reflection = new ReflectionClass($engine);
+        $property = $reflection->getProperty('_httpClient');
+        $property->setAccessible(true);
+        $property->setValue($engine, $client);
+    }
@@
-        $reflection = new ReflectionClass($engine);
-        $property = $reflection->getProperty('_httpClient');
-        $property->setAccessible(true);
-        $property->setValue($engine, $client);
+        $this->_injectHttpClient($engine, $client);
@@
-        $reflection = new ReflectionClass($engine);
-        $property = $reflection->getProperty('_httpClient');
-        $property->setAccessible(true);
-        $property->setValue($engine, $client);
+        $this->_injectHttpClient($engine, $client);

Also applies to: 94-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/LingoDotDevEngineTest.php` around lines 57 - 60, Extract the duplicated
reflection block that sets the private _httpClient into a single test helper
method (e.g., setHttpClientOnEngine($engine, $client)) and use it in both places
instead of repeating the ReflectionClass logic; inside the helper perform the
ReflectionClass($engine), getProperty('_httpClient'), setAccessible(true) and
setValue($engine, $client) so both occurrences (the block using ReflectionClass,
$property, setAccessible, setValue) are replaced with a single call to that
helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/LingoDotDevEngine.php`:
- Around line 70-72: The current check only uses empty() on
$this->config['engineId'] in LingoDotDevEngine and can allow whitespace or
reserved chars that break URL paths; replace that with a trimmed value and
strict validation (e.g., ensure $engineId = trim($this->config['engineId']) and
validate against an allowed character set such as alphanumerics, hyphen,
underscore via a regex) and throw \InvalidArgumentException if it fails, and
when constructing the request path (the method that concatenates the engine ID
into the URL) use proper URL-encoding (rawurlencode) of the engine ID instead of
direct concatenation; apply the same validation/encoding at the other occurrence
referenced (line ~163).
- Around line 58-63: The client lacks default timeouts: update the config merge
that sets $this->config (the array_merge block) to include sensible defaults
such as 'timeout' and 'connect_timeout' (e.g. timeout => 10, connect_timeout =>
5) and ensure these keys are passed through to the Guzzle client constructor
where the Client is instantiated (the new Client(...) usage around the later
block referenced 88-96) so callers cannot block indefinitely; preserve any
user-supplied overrides from $config while adding these defaults.

In `@test-all-methods.php`:
- Around line 41-52: Change the catch from catching \Exception to catching
\Throwable so fatal PHP errors (TypeError, ParseError, etc.) are handled by the
test harness: replace "catch (\Exception $e)" with "catch (\Throwable $e)". Keep
the existing RequestException-specific logic (the instanceof RequestException &
hasResponse() block) but guard it safely when $e may not be an Exception (e.g.,
ensure the RequestException class is referenced correctly or fully qualified as
needed, so the block still runs for Guzzle's RequestException).

---

Nitpick comments:
In `@tests/LingoDotDevEngineTest.php`:
- Around line 57-60: Extract the duplicated reflection block that sets the
private _httpClient into a single test helper method (e.g.,
setHttpClientOnEngine($engine, $client)) and use it in both places instead of
repeating the ReflectionClass logic; inside the helper perform the
ReflectionClass($engine), getProperty('_httpClient'), setAccessible(true) and
setValue($engine, $client) so both occurrences (the block using ReflectionClass,
$property, setAccessible, setValue) are replaced with a single call to that
helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e9f60a6-4b21-42f2-b164-7f9cf5a0c109

📥 Commits

Reviewing files that changed from the base of the PR and between cde45bd and 0533ab1.

📒 Files selected for processing (5)
  • README.md
  • composer.json
  • src/LingoDotDevEngine.php
  • test-all-methods.php
  • tests/LingoDotDevEngineTest.php

Comment on lines 58 to 63
$this->config = array_merge(
[
'apiUrl' => 'https://engine.lingo.dev',
'apiUrl' => 'https://api.lingo.dev',
'batchSize' => 25,
'idealBatchItemSize' => 250
], $config
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify timeout configuration in the SDK HTTP client
echo "=== Checking for Client instantiation ==="
rg -nP 'new\s+Client\s*\(' src/LingoDotDevEngine.php -A 15

echo ""
echo "=== Checking for timeout/connect_timeout references ==="
rg -nP "'(timeout|connect_timeout)'\s*=>" src/LingoDotDevEngine.php

echo ""
echo "=== Checking full config method to see default values ==="
sed -n '55,100p' src/LingoDotDevEngine.php

Repository: lingodotdev/sdk-php

Length of output: 2448


🏁 Script executed:

#!/bin/bash
# Verify the HTTP client class source and check for Guzzle imports
echo "=== Checking imports at top of file ==="
head -20 src/LingoDotDevEngine.php

echo ""
echo "=== Searching for Client class usage pattern ==="
rg -n "Client" src/LingoDotDevEngine.php | head -5

echo ""
echo "=== Check if other files use timeout configuration ==="
rg -rn "timeout|connect_timeout" src/ --type=php

Repository: lingodotdev/sdk-php

Length of output: 899


🌐 Web query:

GuzzleHttp Client timeout connect_timeout options configuration

💡 Result:

In Guzzle (GuzzleHttp\Client), timeout and connect_timeout are request options (floats in seconds) that you pass either per request or as defaults when constructing the client.

What they mean

  • timeout: total time allowed for the whole request (0 = wait indefinitely). (docs.guzzlephp.org)
  • connect_timeout: time allowed to establish the TCP connection (0 = wait indefinitely). Note: only supported by Guzzle’s built-in cURL handler. (docs.guzzlephp.org)

Set per request

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://example.com', [
    'connect_timeout' => 2.0,  // seconds to connect
    'timeout'         => 10.0, // total request seconds
]);

(Examples/definitions match Guzzle’s request options docs.) (docs.guzzlephp.org)

Set defaults on the client (applies to all requests unless overridden)

$client = new \GuzzleHttp\Client([
    'connect_timeout' => 2.0,
    'timeout'         => 10.0,
]);

// override for one call
$response = $client->request('GET', 'https://example.com', [
    'timeout' => 3.0,
]);

Related option you might want

  • read_timeout: timeout for individual read operations when streaming a response body (defaults to PHP default_socket_timeout). (docs.guzzlephp.org)

Practical note

Leaving timeouts at 0 (infinite) can cause requests to hang indefinitely; many projects recommend setting non-zero values in production. (docs.typo3.org)

Citations:


Add client timeouts to prevent indefinite network blocking.

The SDK client is created without timeout or connect_timeout options, allowing stalled upstream calls to block callers indefinitely. GuzzleHTTP supports these as constructor options and defaults to infinite wait time when unset.

🛡️ Proposed fix
         $this->config = array_merge(
             [
             'apiUrl' => 'https://api.lingo.dev',
             'batchSize' => 25,
-            'idealBatchItemSize' => 250
+            'idealBatchItemSize' => 250,
+            'timeout' => 30.0,
+            'connectTimeout' => 10.0
             ], $config
         );
@@
+        if (!is_numeric($this->config['timeout']) || $this->config['timeout'] <= 0) {
+            throw new \InvalidArgumentException('Timeout must be a positive number');
+        }
+
+        if (!is_numeric($this->config['connectTimeout']) || $this->config['connectTimeout'] <= 0) {
+            throw new \InvalidArgumentException('Connect timeout must be a positive number');
+        }
+
         $this->_httpClient = new Client(
             [
             'base_uri' => $this->config['apiUrl'],
             'headers' => [
                 'Content-Type' => 'application/json; charset=utf-8',
                 'X-API-Key' => $this->config['apiKey']
-            ]
+            ],
+            'timeout' => (float)$this->config['timeout'],
+            'connect_timeout' => (float)$this->config['connectTimeout']
             ]
         );

Also applies to: 88-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/LingoDotDevEngine.php` around lines 58 - 63, The client lacks default
timeouts: update the config merge that sets $this->config (the array_merge
block) to include sensible defaults such as 'timeout' and 'connect_timeout'
(e.g. timeout => 10, connect_timeout => 5) and ensure these keys are passed
through to the Guzzle client constructor where the Client is instantiated (the
new Client(...) usage around the later block referenced 88-96) so callers cannot
block indefinitely; preserve any user-supplied overrides from $config while
adding these defaults.

Comment on lines +70 to +72
if (empty($this->config['engineId'])) {
throw new \InvalidArgumentException('Engine ID is required');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Harden engineId validation before path construction.

engineId is only checked with empty(). Whitespace/reserved characters can still produce malformed request paths when concatenated into the URL.

🔒 Proposed fix
-        if (empty($this->config['engineId'])) {
+        if (!is_string($this->config['engineId']) || trim($this->config['engineId']) === '') {
             throw new \InvalidArgumentException('Engine ID is required');
         }
+        $this->config['engineId'] = trim($this->config['engineId']);
@@
-            $url = '/process/' . $this->config['engineId'] . '/localize';
+            $url = '/process/' . rawurlencode($this->config['engineId']) . '/localize';

Also applies to: 163-163

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/LingoDotDevEngine.php` around lines 70 - 72, The current check only uses
empty() on $this->config['engineId'] in LingoDotDevEngine and can allow
whitespace or reserved chars that break URL paths; replace that with a trimmed
value and strict validation (e.g., ensure $engineId =
trim($this->config['engineId']) and validate against an allowed character set
such as alphanumerics, hyphen, underscore via a regex) and throw
\InvalidArgumentException if it fails, and when constructing the request path
(the method that concatenates the engine ID into the URL) use proper
URL-encoding (rawurlencode) of the engine ID instead of direct concatenation;
apply the same validation/encoding at the other occurrence referenced (line
~163).

Comment on lines 41 to 52
} catch (\Exception $e) {
echo "❌ Test failed!\n";
echo "Error: " . $e->getMessage() . "\n";

if ($e instanceof RequestException && $e->hasResponse()) {
$response = $e->getResponse();
echo "Status Code: " . $response->getStatusCode() . "\n";
echo "Response Body: " . $response->getBody() . "\n";
}
$failed++;
return false;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, verify the file exists and examine the relevant lines
if [ -f "test-all-methods.php" ]; then
  echo "=== File found. Examining lines 35-55 for context ==="
  sed -n '35,55p' test-all-methods.php | cat -n
else
  echo "File not found at root. Searching for it..."
  find . -name "test-all-methods.php" -type f
fi

Repository: lingodotdev/sdk-php

Length of output: 915


🏁 Script executed:

# Run the exact grep commands suggested in the review
echo "=== Checking for Exception catches ==="
rg -n 'catch\s*\(\\Exception\s+\$e\)' test-all-methods.php || echo "No matches with exact pattern"

echo "=== Checking for Throwable catches ==="
rg -n 'catch\s*\(\\Throwable\s+\$e\)' test-all-methods.php || echo "No matches with exact pattern"

echo "=== Broader search for catch blocks ==="
rg -n 'catch\s*\(' test-all-methods.php

Repository: lingodotdev/sdk-php

Length of output: 276


🏁 Script executed:

# Check how runTest is called and if there's broader error handling
echo "=== Looking for runTest() calls ==="
rg -n 'runTest' test-all-methods.php

echo "=== Checking for set_error_handler or register_shutdown ==="
rg -n 'set_error_handler|register_shutdown_function|set_exception_handler' test-all-methods.php

echo "=== Looking for outer try-catch blocks ==="
rg -n 'try\s*{' test-all-methods.php

Repository: lingodotdev/sdk-php

Length of output: 816


🏁 Script executed:

# Check the full structure around the runTest calls to understand error handling
head -100 test-all-methods.php | tail -50

Repository: lingodotdev/sdk-php

Length of output: 1474


🏁 Script executed:

# Check what comes after the catch block to see the summary logic
sed -n '50,80p' test-all-methods.php | cat -n

Repository: lingodotdev/sdk-php

Length of output: 1162


🏁 Script executed:

# Check if the SDK might throw Errors or TypeErrors that wouldn't be caught
rg -n 'throw.*Error|TypeError' . --type php | head -20

Repository: lingodotdev/sdk-php

Length of output: 307


Catch \Throwable to keep the harness failure-safe.

Right now, non-Exception errors (for example TypeError) bypass this block and can skip the final summary/exit logic.

🔧 Proposed fix
-    } catch (\Exception $e) {
+    } catch (\Throwable $e) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (\Exception $e) {
echo "❌ Test failed!\n";
echo "Error: " . $e->getMessage() . "\n";
if ($e instanceof RequestException && $e->hasResponse()) {
$response = $e->getResponse();
echo "Status Code: " . $response->getStatusCode() . "\n";
echo "Response Body: " . $response->getBody() . "\n";
}
$failed++;
return false;
}
} catch (\Throwable $e) {
echo "❌ Test failed!\n";
echo "Error: " . $e->getMessage() . "\n";
if ($e instanceof RequestException && $e->hasResponse()) {
$response = $e->getResponse();
echo "Status Code: " . $response->getStatusCode() . "\n";
echo "Response Body: " . $response->getBody() . "\n";
}
$failed++;
return false;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test-all-methods.php` around lines 41 - 52, Change the catch from catching
\Exception to catching \Throwable so fatal PHP errors (TypeError, ParseError,
etc.) are handled by the test harness: replace "catch (\Exception $e)" with
"catch (\Throwable $e)". Keep the existing RequestException-specific logic (the
instanceof RequestException & hasResponse() block) but guard it safely when $e
may not be an Exception (e.g., ensure the RequestException class is referenced
correctly or fully qualified as needed, so the block still runs for Guzzle's
RequestException).

@AndreyHirsa AndreyHirsa marked this pull request as draft March 4, 2026 12:40
@AndreyHirsa AndreyHirsa marked this pull request as ready for review March 5, 2026 20:42
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/LingoDotDevEngine.php (1)

225-241: ⚠️ Potential issue | 🟠 Major

Chunking currently allows payloads to exceed configured word limits.

The record is added before the size check, so chunks can exceed idealBatchItemSize (and README documents this as a max). This can create oversized requests.

🔧 Proposed fix
-        $currentChunkItemCount = 0;
+        $currentChunkItemCount = 0;
+        $currentChunkWordCount = 0;
@@
-            $currentChunk[$key] = $value;
-            $currentChunkItemCount++;
-
-            $currentChunkSize = $this->_countWordsInRecord($currentChunk);
-
-            if ($currentChunkSize > $this->config['idealBatchItemSize']
-                || $currentChunkItemCount >= $this->config['batchSize']
-                || $i === count($keys) - 1
-            ) {
-                $result[] = $currentChunk;
-                $currentChunk = [];
-                $currentChunkItemCount = 0;
-            }
+            $itemWordCount = $this->_countWordsInRecord($value);
+            $wouldExceedWordLimit = !empty($currentChunk)
+                && ($currentChunkWordCount + $itemWordCount > $this->config['idealBatchItemSize']);
+            $wouldExceedBatchLimit = $currentChunkItemCount >= $this->config['batchSize'];
+
+            if ($wouldExceedWordLimit || $wouldExceedBatchLimit) {
+                $result[] = $currentChunk;
+                $currentChunk = [];
+                $currentChunkItemCount = 0;
+                $currentChunkWordCount = 0;
+            }
+
+            $currentChunk[$key] = $value;
+            $currentChunkItemCount++;
+            $currentChunkWordCount += $itemWordCount;
+
+            if ($i === count($keys) - 1) {
+                $result[] = $currentChunk;
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/LingoDotDevEngine.php` around lines 225 - 241, In LingoDotDevEngine's
chunking loop the code adds $value to $currentChunk before checking size,
allowing chunks to exceed config['idealBatchItemSize']; change the logic in the
loop that iterates $keys so you compute the size impact before pushing the item:
call _countWordsInRecord (or compute projected size) for the currentChunk plus
the candidate $value and if that projected size would exceed
config['idealBatchItemSize'] or currentChunkItemCount would hit
config['batchSize'] then push the existing $currentChunk to $result and reset it
before adding the new key/value; ensure the edge-case where a single payload
entry itself exceeds idealBatchItemSize is handled by allowing it to be placed
in its own chunk (i.e., if currentChunk is empty still add the oversized item
and then push).
🧹 Nitpick comments (1)
tests/LingoDotDevEngineTest.php (1)

40-63: Consider consolidating mock-engine setup helpers to reduce duplication.

Both helpers repeat client construction + reflection injection. A single helper with optional history wiring would simplify maintenance.

Also applies to: 74-100

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/LingoDotDevEngineTest.php` around lines 40 - 63, Consolidate the
duplicated test setup by replacing _createMockEngine and the similar helper at
lines 74-100 with a single helper (e.g., createMockEngine) that accepts
$responses and an optional &$history/container parameter; inside the helper
create the MockHandler and HandlerStack, and if a history container is provided,
push Guzzle's Middleware::history($container) onto the HandlerStack before
constructing the Client, then inject the Client into the LingoDotDevEngine
instance via ReflectionClass targeting the _httpClient property
(setAccessible(true) and setValue), and return the engine (and set the passed-in
history container) so tests can optionally capture request history without
duplicating client/reflection wiring.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/LingoDotDevEngine.php`:
- Around line 225-241: In LingoDotDevEngine's chunking loop the code adds $value
to $currentChunk before checking size, allowing chunks to exceed
config['idealBatchItemSize']; change the logic in the loop that iterates $keys
so you compute the size impact before pushing the item: call _countWordsInRecord
(or compute projected size) for the currentChunk plus the candidate $value and
if that projected size would exceed config['idealBatchItemSize'] or
currentChunkItemCount would hit config['batchSize'] then push the existing
$currentChunk to $result and reset it before adding the new key/value; ensure
the edge-case where a single payload entry itself exceeds idealBatchItemSize is
handled by allowing it to be placed in its own chunk (i.e., if currentChunk is
empty still add the oversized item and then push).

---

Nitpick comments:
In `@tests/LingoDotDevEngineTest.php`:
- Around line 40-63: Consolidate the duplicated test setup by replacing
_createMockEngine and the similar helper at lines 74-100 with a single helper
(e.g., createMockEngine) that accepts $responses and an optional
&$history/container parameter; inside the helper create the MockHandler and
HandlerStack, and if a history container is provided, push Guzzle's
Middleware::history($container) onto the HandlerStack before constructing the
Client, then inject the Client into the LingoDotDevEngine instance via
ReflectionClass targeting the _httpClient property (setAccessible(true) and
setValue), and return the engine (and set the passed-in history container) so
tests can optionally capture request history without duplicating
client/reflection wiring.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bb35cca8-2ccf-47be-9c16-4b51d330a9de

📥 Commits

Reviewing files that changed from the base of the PR and between 0533ab1 and 24db355.

📒 Files selected for processing (4)
  • README.md
  • src/LingoDotDevEngine.php
  • test-all-methods.php
  • tests/LingoDotDevEngineTest.php

@AndreyHirsa AndreyHirsa merged commit f76feba into main Mar 7, 2026
2 checks passed
@AndreyHirsa AndreyHirsa deleted the feat/v-next branch March 7, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant