Skip to content

Commit 1e9a16a

Browse files
authored
AVRO-4190: [php] Add logical types (#3526)
* wip * update logical types * wip - added logical types * update PHP workflow * add composer.json to workflow path * bump composer version * add license to AvroLogicalType.php * add test on logical types * update test and fix default decimal value * improve exception messages * fix lint * improve types * add test for decimal schema validation * add test for decimal schema validation * remove is_numeric * add some test for decimal logical type (bytes) * Add AvroDuration type and extend fixed schema * additional tests for decimal
1 parent 1110d25 commit 1e9a16a

16 files changed

Lines changed: 1518 additions & 302 deletions

.github/workflows/test-lang-php.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ on:
2222
branches: [ main ]
2323
paths:
2424
- .github/workflows/test-lang-php.yml
25+
- composer.json
2526
- lang/php/**
2627

2728
defaults:
@@ -44,6 +45,10 @@ jobs:
4445
- '7.3'
4546
- '7.4'
4647
- '8.0'
48+
- '8.1'
49+
- '8.2'
50+
- '8.3'
51+
- '8.4'
4752

4853
steps:
4954
- uses: actions/checkout@v5
@@ -52,7 +57,7 @@ jobs:
5257
uses: shivammathur/setup-php@v2
5358
with:
5459
php-version: ${{ matrix.php }}
55-
tools: composer:2.2.5
60+
tools: composer:2.8.12
5661

5762
- name: Get Composer Cache Directory
5863
id: composer-cache
@@ -82,6 +87,10 @@ jobs:
8287
- '7.3'
8388
- '7.4'
8489
- '8.0'
90+
- '8.1'
91+
- '8.2'
92+
- '8.3'
93+
- '8.4'
8594

8695
steps:
8796
- uses: actions/checkout@v5
@@ -90,7 +99,7 @@ jobs:
9099
uses: shivammathur/setup-php@v2
91100
with:
92101
php-version: ${{ matrix.php }}
93-
tools: composer:2.2.5
102+
tools: composer:2.8.12
94103

95104
- name: Cache Local Maven Repository
96105
uses: actions/cache@v4

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"issues": "https://issues.apache.org/jira/browse/AVRO"
2424
},
2525
"require": {
26-
"php": "^7.1 || ^8.0"
26+
"php": "^7.3 || ^8.0"
2727
},
2828
"deps": [
2929
"vendor/phpunit/phpunit",

lang/php/lib/DataFile/AvroDataIOWriter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ private static function generateSyncMarker()
136136
/**
137137
* Writes the header of the AvroIO object container
138138
*/
139-
private function writeHeader()
139+
private function writeHeader(): void
140140
{
141141
$this->write(AvroDataIO::magic());
142142
$this->datum_writer->writeData(

lang/php/lib/Datum/AvroIOBinaryEncoder.php

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
namespace Apache\Avro\Datum;
2222

2323
use Apache\Avro\Avro;
24+
use Apache\Avro\AvroException;
2425
use Apache\Avro\AvroGMP;
2526
use Apache\Avro\AvroIO;
2627

@@ -41,7 +42,7 @@ class AvroIOBinaryEncoder
4142
* @param AvroIO $io object to which data is to be written.
4243
*
4344
*/
44-
public function __construct($io)
45+
public function __construct(AvroIO $io)
4546
{
4647
Avro::checkPlatform();
4748
$this->io = $io;
@@ -50,9 +51,9 @@ public function __construct($io)
5051
/**
5152
* @param null $datum actual value is ignored
5253
*/
53-
public function writeNull($datum)
54+
public function writeNull($datum): void
5455
{
55-
return null;
56+
return;
5657
}
5758

5859
/**
@@ -67,23 +68,23 @@ public function writeBoolean($datum)
6768
/**
6869
* @param string $datum
6970
*/
70-
public function write($datum)
71+
public function write($datum): void
7172
{
7273
$this->io->write($datum);
7374
}
7475

7576
/**
7677
* @param int $datum
7778
*/
78-
public function writeInt($datum)
79+
public function writeInt($datum): void
7980
{
8081
$this->writeLong($datum);
8182
}
8283

8384
/**
8485
* @param int $n
8586
*/
86-
public function writeLong($n)
87+
public function writeLong($n): void
8788
{
8889
if (Avro::usesGmp()) {
8990
$this->write(AvroGMP::encodeLong($n));
@@ -125,7 +126,7 @@ public static function encodeLong($n)
125126
* @param float $datum
126127
* @uses self::floatToIntBits()
127128
*/
128-
public function writeFloat($datum)
129+
public function writeFloat($datum): void
129130
{
130131
$this->write(self::floatToIntBits($datum));
131132
}
@@ -142,7 +143,7 @@ public function writeFloat($datum)
142143
* @returns string bytes
143144
* @see Avro::checkPlatform()
144145
*/
145-
public static function floatToIntBits($float)
146+
public static function floatToIntBits($float): string
146147
{
147148
return pack('g', (float) $float);
148149
}
@@ -151,7 +152,7 @@ public static function floatToIntBits($float)
151152
* @param float $datum
152153
* @uses self::doubleToLongBits()
153154
*/
154-
public function writeDouble($datum)
155+
public function writeDouble($datum): void
155156
{
156157
$this->write(self::doubleToLongBits($datum));
157158
}
@@ -165,7 +166,7 @@ public function writeDouble($datum)
165166
* @param double $double
166167
* @returns string bytes
167168
*/
168-
public static function doubleToLongBits($double)
169+
public static function doubleToLongBits($double): string
169170
{
170171
return pack('e', (double) $double);
171172
}
@@ -174,17 +175,64 @@ public static function doubleToLongBits($double)
174175
* @param string $str
175176
* @uses self::writeBytes()
176177
*/
177-
public function writeString($str)
178+
public function writeString($str): void
178179
{
179180
$this->writeBytes($str);
180181
}
181182

182183
/**
183184
* @param string $bytes
184185
*/
185-
public function writeBytes($bytes)
186+
public function writeBytes($bytes): void
186187
{
187188
$this->writeLong(strlen($bytes));
188189
$this->write($bytes);
189190
}
191+
192+
public function writeDecimal($decimal, int $scale, int $precision): void
193+
{
194+
if (!is_numeric($decimal)) {
195+
throw new AvroException("Decimal value '{$decimal}' must be numeric");
196+
}
197+
198+
$value = $decimal * (10 ** $scale);
199+
if (!is_int($value)) {
200+
$value = (int) round($value);
201+
}
202+
203+
$maxValue = 10 ** $precision;
204+
if (abs($value) >= $maxValue) {
205+
throw new AvroException(
206+
"Decimal value '{$decimal}' is out of range for precision={$precision}, scale={$scale}"
207+
);
208+
}
209+
210+
$packed = pack('J', $value);
211+
212+
$significantBit = self::getMostSignificantBitAt($packed, 0);
213+
$trimByte = $significantBit ? 0xff : 0x00;
214+
215+
$offset = 0;
216+
$packedLength = strlen($packed);
217+
while ($offset < $packedLength - 1) {
218+
if (ord($packed[$offset]) !== $trimByte) {
219+
break;
220+
}
221+
222+
if (self::getMostSignificantBitAt($packed, $offset + 1) !== $significantBit) {
223+
break;
224+
}
225+
226+
$offset++;
227+
}
228+
229+
$value = substr($packed, $offset);
230+
231+
$this->writeBytes($value);
232+
}
233+
234+
private static function getMostSignificantBitAt($bytes, $offset): int
235+
{
236+
return ord($bytes[$offset]) & 0x80;
237+
}
190238
}

0 commit comments

Comments
 (0)