@@ -117,7 +117,7 @@ function write_data($writers_schema, $datum, $encoder)
117117 case AvroSchema::STRING_TYPE :
118118 return $ encoder ->write_string ($ datum );
119119 case AvroSchema::BYTES_TYPE :
120- return $ encoder ->write_bytes ($ datum );
120+ return $ encoder ->write_bytes ($ writers_schema , $ datum );
121121 case AvroSchema::ARRAY_SCHEMA :
122122 return $ this ->write_array ($ writers_schema , $ datum , $ encoder );
123123 case AvroSchema::MAP_SCHEMA :
@@ -386,13 +386,25 @@ public function write_double($datum)
386386 * @param string $str
387387 * @uses self::write_bytes()
388388 */
389- function write_string ($ str ) { $ this ->write_bytes ($ str ); }
389+ function write_string ($ str ) { $ this ->write_bytes (null , $ str ); }
390390
391391 /**
392+ * @param AvroSchema|null $writers_schema
392393 * @param string $bytes
394+ * @throws AvroException
393395 */
394- function write_bytes ($ bytes )
396+ function write_bytes ($ writers_schema , $ bytes )
395397 {
398+ if ($ writers_schema !== null && $ writers_schema ->logical_type () === 'decimal ' ) {
399+ $ scale = $ writers_schema ->extra_attributes ()['scale ' ] ?? 0 ;
400+ $ precision = $ writers_schema ->extra_attributes ()['precision ' ] ?? null ;
401+ if ($ precision === null ) {
402+ throw new AvroException ('Decimal precision is required ' );
403+ }
404+
405+ $ bytes = self ::decimal_to_bytes ($ bytes , $ scale , $ precision );
406+ }
407+
396408 $ this ->write_long (strlen ($ bytes ));
397409 $ this ->write ($ bytes );
398410 }
@@ -401,6 +413,49 @@ function write_bytes($bytes)
401413 * @param string $datum
402414 */
403415 function write ($ datum ) { $ this ->io ->write ($ datum ); }
416+
417+ /**
418+ * @throws AvroException
419+ */
420+ private static function decimal_to_bytes ($ decimal , int $ scale , int $ precision ): string
421+ {
422+ if (!is_numeric ($ decimal )) {
423+ throw new AvroException ('Decimal must be a numeric value ' );
424+ }
425+
426+ $ value = $ decimal * (10 ** $ scale );
427+ if (!is_int ($ value )) {
428+ $ value = (int )round ($ value );
429+ }
430+ if (abs ($ value ) > (10 ** $ precision - 1 )) {
431+ throw new AvroException ('Decimal value is out of range ' );
432+ }
433+
434+ $ packed = pack ('J ' , $ value );
435+ $ significantBit = self ::getMostSignificantBitAt ($ packed , 0 );
436+ $ trimByte = $ significantBit ? 0xff : 0x00 ;
437+
438+ $ offset = 0 ;
439+ $ packedLength = strlen ($ packed );
440+ while ($ offset < $ packedLength - 1 ) {
441+ if (ord ($ packed [$ offset ]) !== $ trimByte ) {
442+ break ;
443+ }
444+
445+ if (self ::getMostSignificantBitAt ($ packed , $ offset + 1 ) !== $ significantBit ) {
446+ break ;
447+ }
448+
449+ $ offset ++;
450+ }
451+
452+ return substr ($ packed , $ offset );
453+ }
454+
455+ private static function getMostSignificantBitAt ($ bytes , $ offset ): int
456+ {
457+ return ord ($ bytes [$ offset ]) & 0x80 ;
458+ }
404459}
405460
406461/**
@@ -925,8 +980,10 @@ static public function long_bits_to_double($bits)
925980 */
926981 static public function bytes_to_decimal ($ bytes , $ scale = 0 )
927982 {
928- $ int = hexdec (bin2hex ($ bytes ));
929- return $ scale > 0 ? ($ int / (10 ** $ scale )) : $ int ;
983+ $ mostSignificantBit = ord ($ bytes [0 ]) & 0x80 ;
984+ $ padded = str_pad ($ bytes , 8 , $ mostSignificantBit ? "\xff" : "\x00" , STR_PAD_LEFT );
985+ $ int = unpack ('J ' , $ padded )[1 ];
986+ return $ scale > 0 ? ($ int / (10 ** $ scale )) : $ int ;
930987 }
931988
932989 /**
0 commit comments