Skip to content

Commit 42d91f9

Browse files
committed
2 parents 08b2e0b + a255929 commit 42d91f9

File tree

3 files changed

+254
-6
lines changed

3 files changed

+254
-6
lines changed

src/ODataResponse.php

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ public function __construct($request, $body = null, $httpStatusCode = null, $hea
8787
*/
8888
private function decodeBody()
8989
{
90-
$decodedBody = json_decode($this->body, true);
90+
$decodedBody = json_decode($this->body, true, 512, JSON_BIGINT_AS_STRING);
9191
if ($decodedBody === null) {
9292
$matches = null;
9393
preg_match('~\{(?:[^{}]|(?R))*\}~', $this->body, $matches);
94-
$decodedBody = json_decode($matches[0], true);
94+
$decodedBody = json_decode($matches[0], true, 512, JSON_BIGINT_AS_STRING);
9595
if ($decodedBody === null) {
9696
$decodedBody = array();
9797
}
@@ -153,11 +153,19 @@ public function getResponseAsObject($returnType)
153153

154154
//If more than one object is returned
155155
if (array_key_exists(Constants::ODATA_VALUE, $result)) {
156-
$objArray = array();
157-
foreach ($result[Constants::ODATA_VALUE] as $obj) {
158-
$objArray[] = new $class($obj);
156+
$value = $result[Constants::ODATA_VALUE];
157+
158+
// Check if value is an array before iterating
159+
if (is_array($value)) {
160+
$objArray = array();
161+
foreach ($value as $obj) {
162+
$objArray[] = new $class($obj);
163+
}
164+
return $objArray;
165+
} else {
166+
// Single value case - wrap in array and create single object
167+
return [new $class(['value' => $value])];
159168
}
160-
return $objArray;
161169
} else {
162170
return [new $class($result)];
163171
}

tests/LargeNumbersTest.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace SaintSystems\OData\Tests;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use SaintSystems\OData\ODataResponse;
7+
8+
class LargeNumbersTest extends TestCase
9+
{
10+
public function testLargeNumberPreservation()
11+
{
12+
// Test that large numbers are preserved as strings instead of converted to scientific notation
13+
$largeNumber = '80000000000000000000000';
14+
$jsonResponse = '{"id": ' . $largeNumber . ', "name": "test", "smallId": 123}';
15+
16+
$mockRequest = new \stdClass();
17+
$response = new ODataResponse($mockRequest, $jsonResponse, 200, []);
18+
19+
$body = $response->getBody();
20+
21+
// Large number should be preserved as string
22+
$this->assertEquals($largeNumber, $body['id']);
23+
$this->assertIsString($body['id']);
24+
25+
// Small numbers should remain as integers
26+
$this->assertEquals(123, $body['smallId']);
27+
$this->assertIsInt($body['smallId']);
28+
29+
// Other data should be unaffected
30+
$this->assertEquals('test', $body['name']);
31+
$this->assertIsString($body['name']);
32+
}
33+
34+
public function testLargeNumbersInArrayResponse()
35+
{
36+
// Test OData response with array of entities containing large numbers
37+
$largeNumber1 = '80000000000000000000000';
38+
$largeNumber2 = '90000000000000000000000';
39+
40+
$jsonResponse = '{
41+
"value": [
42+
{"id": ' . $largeNumber1 . ', "name": "entity1"},
43+
{"id": ' . $largeNumber2 . ', "name": "entity2"}
44+
]
45+
}';
46+
47+
$mockRequest = new \stdClass();
48+
$response = new ODataResponse($mockRequest, $jsonResponse, 200, []);
49+
50+
$body = $response->getBody();
51+
52+
// Verify large numbers are preserved in array responses
53+
$this->assertEquals($largeNumber1, $body['value'][0]['id']);
54+
$this->assertIsString($body['value'][0]['id']);
55+
56+
$this->assertEquals($largeNumber2, $body['value'][1]['id']);
57+
$this->assertIsString($body['value'][1]['id']);
58+
}
59+
60+
public function testMixedNumberTypesPreservation()
61+
{
62+
// Test that various number types are handled correctly
63+
$jsonResponse = '{
64+
"largeInt": 80000000000000000000000,
65+
"normalInt": 123,
66+
"float": 123.45,
67+
"negativeInt": -456,
68+
"largeLong": 9223372036854775808
69+
}';
70+
71+
$mockRequest = new \stdClass();
72+
$response = new ODataResponse($mockRequest, $jsonResponse, 200, []);
73+
74+
$body = $response->getBody();
75+
76+
// Very large number should be preserved as string
77+
$this->assertEquals('80000000000000000000000', $body['largeInt']);
78+
$this->assertIsString($body['largeInt']);
79+
80+
// Normal integers should remain as integers
81+
$this->assertEquals(123, $body['normalInt']);
82+
$this->assertIsInt($body['normalInt']);
83+
84+
// Floats should remain as floats
85+
$this->assertEquals(123.45, $body['float']);
86+
$this->assertIsFloat($body['float']);
87+
88+
// Negative integers should remain as integers
89+
$this->assertEquals(-456, $body['negativeInt']);
90+
$this->assertIsInt($body['negativeInt']);
91+
92+
// Numbers larger than PHP_INT_MAX should be strings
93+
$this->assertEquals('9223372036854775808', $body['largeLong']);
94+
$this->assertIsString($body['largeLong']);
95+
}
96+
}

tests/ODataResponseTest.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<?php
2+
3+
namespace SaintSystems\OData\Tests;
4+
5+
// If running standalone, load autoloader
6+
if (basename(__FILE__) === basename($_SERVER['PHP_SELF'])) {
7+
require_once __DIR__ . '/../vendor/autoload.php';
8+
}
9+
10+
use SaintSystems\OData\Entity;
11+
use SaintSystems\OData\ODataResponse;
12+
use SaintSystems\OData\ODataRequest;
13+
14+
// Check if PHPUnit is available, if not provide a fallback
15+
if (class_exists('PHPUnit\Framework\TestCase')) {
16+
class ODataResponseTestBase extends \PHPUnit\Framework\TestCase {}
17+
} else {
18+
// Fallback base class for when PHPUnit is not available
19+
class ODataResponseTestBase {
20+
protected function createMock($className) {
21+
return new \stdClass();
22+
}
23+
protected function assertIsArray($value) {
24+
if (!is_array($value)) throw new \Exception("Expected array");
25+
}
26+
protected function assertCount($expected, $array) {
27+
if (count($array) !== $expected) throw new \Exception("Count mismatch");
28+
}
29+
protected function assertInstanceOf($class, $object) {
30+
if (!($object instanceof $class)) throw new \Exception("Instance mismatch");
31+
}
32+
}
33+
}
34+
35+
class ODataResponseTest extends ODataResponseTestBase
36+
{
37+
public function testGetResponseAsObjectWithArrayValue()
38+
{
39+
// Test case for traditional array response
40+
$responseBody = json_encode([
41+
'value' => [
42+
['id' => 1, 'name' => 'Test 1'],
43+
['id' => 2, 'name' => 'Test 2']
44+
]
45+
]);
46+
47+
$request = $this->createMock(ODataRequest::class);
48+
$response = new ODataResponse($request, $responseBody, 200, []);
49+
50+
$result = $response->getResponseAsObject(Entity::class);
51+
52+
$this->assertIsArray($result);
53+
$this->assertCount(2, $result);
54+
$this->assertInstanceOf(Entity::class, $result[0]);
55+
$this->assertInstanceOf(Entity::class, $result[1]);
56+
}
57+
58+
public function testGetResponseAsObjectWithStringValue()
59+
{
60+
// Test case for single string value response (the reported issue)
61+
$responseBody = json_encode([
62+
'value' => 'uniqueIDofCreatedOrder'
63+
]);
64+
65+
$request = $this->createMock(ODataRequest::class);
66+
$response = new ODataResponse($request, $responseBody, 200, []);
67+
68+
$result = $response->getResponseAsObject(Entity::class);
69+
70+
$this->assertIsArray($result);
71+
$this->assertCount(1, $result);
72+
$this->assertInstanceOf(Entity::class, $result[0]);
73+
74+
// Verify the value is properly stored
75+
$entity = $result[0];
76+
$properties = $entity->getProperties();
77+
if (!isset($properties['value']) || $properties['value'] !== 'uniqueIDofCreatedOrder') {
78+
throw new \Exception("Value not properly stored in Entity");
79+
}
80+
}
81+
82+
public function testGetResponseAsObjectWithNumericValue()
83+
{
84+
// Test case for single numeric value response
85+
$responseBody = json_encode([
86+
'value' => 12345
87+
]);
88+
89+
$request = $this->createMock(ODataRequest::class);
90+
$response = new ODataResponse($request, $responseBody, 200, []);
91+
92+
$result = $response->getResponseAsObject(Entity::class);
93+
94+
$this->assertIsArray($result);
95+
$this->assertCount(1, $result);
96+
$this->assertInstanceOf(Entity::class, $result[0]);
97+
98+
// Verify the value is properly stored
99+
$entity = $result[0];
100+
$properties = $entity->getProperties();
101+
if (!isset($properties['value']) || $properties['value'] !== 12345) {
102+
throw new \Exception("Numeric value not properly stored in Entity");
103+
}
104+
}
105+
106+
public function testGetResponseAsObjectWithoutValueKey()
107+
{
108+
// Test case for response without "value" key
109+
$responseBody = json_encode([
110+
'id' => 1,
111+
'name' => 'Test'
112+
]);
113+
114+
$request = $this->createMock(ODataRequest::class);
115+
$response = new ODataResponse($request, $responseBody, 200, []);
116+
117+
$result = $response->getResponseAsObject(Entity::class);
118+
119+
$this->assertIsArray($result);
120+
$this->assertCount(1, $result);
121+
$this->assertInstanceOf(Entity::class, $result[0]);
122+
}
123+
}
124+
125+
// If running standalone (not via PHPUnit), execute the tests
126+
if (!class_exists('PHPUnit\Framework\TestCase') && basename(__FILE__) === basename($_SERVER['PHP_SELF'])) {
127+
echo "Running ODataResponse tests...\n\n";
128+
129+
$test = new ODataResponseTest();
130+
$methods = ['testGetResponseAsObjectWithArrayValue', 'testGetResponseAsObjectWithStringValue',
131+
'testGetResponseAsObjectWithNumericValue', 'testGetResponseAsObjectWithoutValueKey'];
132+
133+
foreach ($methods as $method) {
134+
try {
135+
echo "Running $method... ";
136+
$test->$method();
137+
echo "✓ PASSED\n";
138+
} catch (Exception $e) {
139+
echo "✗ FAILED: " . $e->getMessage() . "\n";
140+
}
141+
}
142+
143+
echo "\nTests completed.\n";
144+
}

0 commit comments

Comments
 (0)