diff --git a/.github/workflows/test-lang-php.yml b/.github/workflows/test-lang-php.yml index 883ef5ce115..6bb145f6100 100644 --- a/.github/workflows/test-lang-php.yml +++ b/.github/workflows/test-lang-php.yml @@ -42,9 +42,6 @@ jobs: - ubuntu-latest - ubuntu-24.04-arm php: - - '7.3' - - '7.4' - - '8.0' - '8.1' - '8.2' - '8.3' @@ -71,8 +68,12 @@ jobs: ${{ runner.os }}-composer- - name: Lint + if: matrix.php.version == '8.1' run: ./build.sh lint + - name: Static analysis + run: ./build.sh phpstan + - name: Test run: ./build.sh test @@ -84,9 +85,6 @@ jobs: - ubuntu-latest - ubuntu-24.04-arm php: - - '7.3' - - '7.4' - - '8.0' - '8.1' - '8.2' - '8.3' diff --git a/composer.json b/composer.json index 3b5e06ef219..ec5d2a3f37b 100644 --- a/composer.json +++ b/composer.json @@ -23,20 +23,21 @@ "issues": "https://issues.apache.org/jira/browse/AVRO" }, "require": { - "php": "^7.3 || ^8.0" + "php": "^8.1" }, "deps": [ - "vendor/phpunit/phpunit", - "vendor/squizlabs/php_codesniffer" + "vendor/phpunit/phpunit" ], "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "squizlabs/php_codesniffer": "3.*", + "phpunit/phpunit": "^10.5", "php-mock/php-mock-phpunit": "^2.10", "ext-json": "*", "ext-xml": "*", "ext-curl": "*", - "ext-pcntl": "*" + "ext-pcntl": "*", + "rector/rector": "^2.2", + "friendsofphp/php-cs-fixer": "^3.89", + "phpstan/phpstan": "^2.1" }, "autoload": { "psr-4": { diff --git a/lang/php/.gitignore b/lang/php/.gitignore index d2429e1abce..e691e824604 100644 --- a/lang/php/.gitignore +++ b/lang/php/.gitignore @@ -1,3 +1,5 @@ pkg vendor .phpunit.result.cache +.phpunit.cache +.php-cs-fixer.cache diff --git a/lang/php/.php-cs-fixer.dist.php b/lang/php/.php-cs-fixer.dist.php new file mode 100644 index 00000000000..84f9fcd711a --- /dev/null +++ b/lang/php/.php-cs-fixer.dist.php @@ -0,0 +1,102 @@ + php-cs-fixer describe blank_line_before_statement + * ``` + */ +$finder = PhpCsFixer\Finder::create() + ->in(['test']) + ->append(['.php-cs-fixer.dist.php']); + +return (new PhpCsFixer\Config()) + ->setRiskyAllowed(true) + ->setParallelConfig(ParallelConfigFactory::detect()) + ->setRules([ + // —— CS Rule Sets ————————————————————————————————————————————————————— + '@Symfony' => true, + '@PHP8x0Migration' => true, + + // —— Overriden rules —————————————————————————————————————————————————— + + // @Symfony: `['statements' => ['return']]` + // Here using: default, `['statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try']]` + 'blank_line_before_statement' => true, + + // @Symfony: `['tokens' => [['attribute', 'case', 'continue', 'curly_brace_block', 'default', 'extra', 'parenthesis_brace_block', 'square_brace_block', 'switch', 'throw', 'use']]]` + // Here using: default, `['tokens' => ['extra']]` + 'no_extra_blank_lines' => true, + + // @Symfony: `['allow_hidden_params' => true, 'remove_inheritdoc' => true]` + 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true, 'remove_inheritdoc' => false], + + // @Symfony: `['after_heredoc' => true]` + // Here using: default, `['after_heredoc' => false]` + 'no_whitespace_before_comma_in_array' => true, + + // @Symfony: `['order' => ['use_trait']]` + // Here using: default, `['order' => ['use_trait', 'case', 'constant_public', 'constant_protected', 'constant_private', 'property_public', 'property_protected', 'property_private', 'construct', 'destruct', 'magic', 'phpunit', 'method_public', 'method_protected', 'method_private']]`) + 'ordered_class_elements' => true, + + // @Symfony: `['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha']` + // Here using: default, all import types (classes, functions, and constants) are sorted together alphabetically without grouping them by type + 'ordered_imports' => true, + + // @Symfony: `['order' => ['param', 'return', 'throws']]` + // Here using: default, `['param', 'throws', 'return']` + 'phpdoc_order' => true, + + // @Symfony: `['null_adjustment' => 'always_last', 'sort_algorithm' => 'none']` + 'phpdoc_types_order' => ['null_adjustment' => 'always_first'], + + // @Symfony: `['case' => 'camel_case']` + 'php_unit_method_casing' => ['case' => 'snake_case'], + + // —— Overriden rules (disabled) ———————————————————————————————————————— + // @Symfony + 'increment_style' => false, + 'phpdoc_align' => false, + 'phpdoc_separation' => false, + 'phpdoc_summary' => false, + 'phpdoc_to_comment' => false, + 'single_line_comment_style' => false, + 'single_line_throw' => false, + 'single_quote' => false, + + // —— Additional rules —————————————————————————————————————————————————— + 'no_useless_else' => true, + 'phpdoc_add_missing_param_annotation' => true, + 'protected_to_private' => true, + 'psr_autoloading' => true, + ]) + ->setFinder($finder) +; diff --git a/lang/php/README.md b/lang/php/README.md index 9aa12d943be..991cfc457a2 100644 --- a/lang/php/README.md +++ b/lang/php/README.md @@ -8,14 +8,14 @@ A library for using [Avro](https://avro.apache.org/) with PHP. Requirements ============ - * PHP 7.3+ + * PHP 8.1+ * On 32-bit platforms, the [GMP PHP extension](https://php.net/gmp) * For Zstandard compression, [ext-zstd](https://github.com/kjdev/php-ext-zstd) * For Snappy compression, [ext-snappy](https://github.com/kjdev/php-ext-snappy) * For testing, [PHPUnit](https://www.phpunit.de/) Both GMP and PHPUnit are often available via package management -systems as `php7-gmp` and `phpunit`, respectively. +systems as `php8.1-gmp` and `phpunit`, respectively. Getting started diff --git a/lang/php/build.sh b/lang/php/build.sh index 8b5497001cb..bb4595f8bb0 100755 --- a/lang/php/build.sh +++ b/lang/php/build.sh @@ -59,13 +59,22 @@ do lint) composer install -d "../.." - find . -name "*.php" -print0 | xargs -0 -n1 -P8 php -l - ../../vendor/bin/phpcs --standard=PSR12 lib + ../../vendor/bin/php-cs-fixer fix --dry-run --show-progress=dots --verbose --diff --no-interaction + ;; + + lint-fix) + composer install -d "../.." + ../../vendor/bin/php-cs-fixer fix --show-progress=dots --verbose --diff + ;; + + phpstan) + composer install -d "../.." + ../../vendor/bin/phpstan ;; test) composer install -d "../.." - ../../vendor/bin/phpunit -v + ../../vendor/bin/phpunit ;; dist) @@ -77,7 +86,7 @@ do ;; *) - echo "Usage: $0 {interop-data-generate|test-interop|lint|test|dist|clean}" + echo "Usage: $0 {interop-data-generate|test-interop|lint|lint-fix|test|dist|clean}" esac done diff --git a/lang/php/examples/write_read.php b/lang/php/examples/write_read.php index b5c1a69b9f3..2c4e8b0e378 100644 --- a/lang/php/examples/write_read.php +++ b/lang/php/examples/write_read.php @@ -37,9 +37,9 @@ {"name":"member_name", "type":"string"}]} _JSON; -$jose = array('member_id' => 1392, 'member_name' => 'Jose'); -$maria = array('member_id' => 1642, 'member_name' => 'Maria'); -$data = array($jose, $maria); +$jose = ['member_id' => 1392, 'member_name' => 'Jose']; +$maria = ['member_id' => 1642, 'member_name' => 'Maria']; +$data = [$jose, $maria]; $file_name = 'data.avr'; // Open $file_name for writing, using the given writer's schema diff --git a/lang/php/lib/Avro.php b/lang/php/lib/Avro.php index 8e032bc52a2..7c537ca8c2d 100644 --- a/lang/php/lib/Avro.php +++ b/lang/php/lib/Avro.php @@ -46,13 +46,12 @@ class Avro private const GMP_BIGINTEGER_MODE = 0x01; /**#@-*/ /** - * @var int * Mode used to handle bigintegers. After Avro::check64Bit() has been called, * (usually via a call to Avro::checkPlatform(), set to * self::GMP_BIGINTEGER_MODE on 32-bit platforms that have GMP available, * and to self::PHP_BIGINTEGER_MODE otherwise. */ - private static $biginteger_mode; + private static int $biginteger_mode; /** * Wrapper method to call each required check. @@ -70,7 +69,7 @@ public static function checkPlatform() */ private static function check64Bit() { - if (8 != PHP_INT_SIZE) { + if (8 !== PHP_INT_SIZE) { if (extension_loaded('gmp')) { self::$biginteger_mode = self::GMP_BIGINTEGER_MODE; } else { @@ -83,11 +82,11 @@ private static function check64Bit() } /** - * @returns boolean true if the PHP GMP extension is used and false otherwise. + * @returns bool true if the PHP GMP extension is used and false otherwise. * @internal Requires Avro::check64Bit() (exposed via Avro::checkPlatform()) * to have been called to set Avro::$biginteger_mode. */ - public static function usesGmp() + public static function usesGmp(): bool { return self::GMP_BIGINTEGER_MODE === self::$biginteger_mode; } diff --git a/lang/php/lib/AvroDebug.php b/lang/php/lib/AvroDebug.php index 777dcd9b24a..d3beb106201 100644 --- a/lang/php/lib/AvroDebug.php +++ b/lang/php/lib/AvroDebug.php @@ -55,12 +55,11 @@ public static function debug($format, $args, $debug_level = self::DEBUG1) } /** - * @var int $debug_level - * @returns boolean true if the given $debug_level is equivalent + * @return boolean true if the given $debug_level is equivalent * or more verbose than than the current debug level * and false otherwise. */ - public static function isDebug($debug_level = self::DEBUG1) + public static function isDebug(int $debug_level = self::DEBUG1): bool { return (self::DEBUG_LEVEL >= $debug_level); } @@ -92,7 +91,7 @@ public static function hexArray($str) */ public static function bytesArray($str, $format = 'x%02x') { - $x = array(); + $x = []; foreach (str_split($str) as $b) { $x[] = sprintf($format, ord($b)); } @@ -151,7 +150,7 @@ public static function asciiArray($str, $format = 'ctrl') throw new AvroException('Unrecognized format specifier'); } - $ctrl_chars = array( + $ctrl_chars = [ 'NUL', 'SOH', 'STX', @@ -184,8 +183,8 @@ public static function asciiArray($str, $format = 'ctrl') 'GS', 'RS', 'US' - ); - $x = array(); + ]; + $x = []; foreach (str_split($str) as $b) { $db = ord($b); if ($db < 32) { diff --git a/lang/php/lib/AvroGMP.php b/lang/php/lib/AvroGMP.php index c868cf17a66..e4309668e4f 100644 --- a/lang/php/lib/AvroGMP.php +++ b/lang/php/lib/AvroGMP.php @@ -32,32 +32,32 @@ class AvroGMP { /** - * @var resource memoized GMP resource for zero + * @var \GMP memoized GMP resource for zero */ private static $gmp_0; /** - * @var resource memoized GMP resource for one (1) + * @var \GMP memoized GMP resource for one (1) */ private static $gmp_1; /** - * @var resource memoized GMP resource for two (2) + * @var \GMP memoized GMP resource for two (2) */ private static $gmp_2; /** - * @var resource memoized GMP resource for 0x7f + * @var \GMP memoized GMP resource for 0x7f */ private static $gmp_0x7f; /** - * @var resource memoized GMP resource for 64-bit ~0x7f + * @var \GMP memoized GMP resource for 64-bit ~0x7f */ private static $gmp_n0x7f; /** - * @var resource memoized GMP resource for 64-bits of 1 + * @var \GMP memoized GMP resource for 64-bits of 1 */ private static $gmp_0xfs; /** - * @param int|str $n integer (or string representation of integer) to encode + * @param int|string $n integer (or string representation of integer) to encode * @return string $bytes of the long $n encoded per the Avro spec */ public static function encodeLong($n) @@ -104,8 +104,8 @@ public static function shiftLeft($g, $shift) } /** - * @param GMP resource - * @returns GMP resource 64-bit two's complement of input. + * @param \GMP $g resource + * @return \GMP resource 64-bit two's complement of input. */ public static function gmpTwosComplement($g) { @@ -147,7 +147,7 @@ public static function shiftRight($g, $shift) // phpcs:disable PSR1.Methods.CamelCapsMethodName /** - * @returns resource GMP resource for two (2) + * @returns \GMP GMP resource for two (2) */ private static function gmp_2() { diff --git a/lang/php/lib/AvroIO.php b/lang/php/lib/AvroIO.php index 30cd94e8066..9a6f5825233 100644 --- a/lang/php/lib/AvroIO.php +++ b/lang/php/lib/AvroIO.php @@ -18,6 +18,8 @@ * limitations under the License. */ +declare(strict_types=1); + namespace Apache\Avro; /** @@ -26,7 +28,7 @@ * * @package Avro */ -class AvroIO +interface AvroIO { /** * @var string general read mode @@ -52,33 +54,21 @@ class AvroIO /** * Read $len bytes from AvroIO instance - * @return string bytes read - * @var int $len */ - public function read($len) - { - throw new AvroNotImplementedException('Not implemented'); - } + public function read(int $len): string; /** * Append bytes to this buffer. (Nothing more is needed to support Avro.) - * @param string $arg bytes to write + * @param string $bytes bytes to write * @returns int count of bytes written. * @throws IO\AvroIOException if $args is not a string value. */ - public function write($arg) - { - throw new AvroNotImplementedException('Not implemented'); - } + public function write(string $bytes): int; /** * Return byte offset within AvroIO instance - * @return int */ - public function tell() - { - throw new AvroNotImplementedException('Not implemented'); - } + public function tell(): int; /** * Set the position indicator. The new position, measured in bytes @@ -92,19 +82,13 @@ public function tell() * * @throws IO\AvroIOException */ - public function seek($offset, $whence = self::SEEK_SET): bool - { - throw new AvroNotImplementedException('Not implemented'); - } + public function seek(int $offset, int $whence = self::SEEK_SET): bool; /** * Flushes any buffered data to the AvroIO object. - * @returns boolean true upon success. + * @returns bool true upon success. */ - public function flush() - { - throw new AvroNotImplementedException('Not implemented'); - } + public function flush(): bool; /** * Returns whether or not the current position at the end of this AvroIO @@ -113,18 +97,12 @@ public function flush() * Note isEof() is not like eof in C or feof in PHP: * it returns TRUE if the *next* read would be end of file, * rather than if the *most recent* read read end of file. - * @returns boolean true if at the end of file, and false otherwise + * @returns bool true if at the end of file, and false otherwise */ - public function isEof() - { - throw new AvroNotImplementedException('Not implemented'); - } + public function isEof(): bool; /** * Closes this AvroIO instance. */ - public function close() - { - throw new AvroNotImplementedException('Not implemented'); - } + public function close(): bool; } diff --git a/lang/php/lib/DataFile/AvroDataIO.php b/lang/php/lib/DataFile/AvroDataIO.php index aaea5bdb140..7fe75854946 100644 --- a/lang/php/lib/DataFile/AvroDataIO.php +++ b/lang/php/lib/DataFile/AvroDataIO.php @@ -99,7 +99,7 @@ class AvroDataIO */ public static function magicSize() { - return strlen(self::magic()); + return strlen((string) self::magic()); } /** diff --git a/lang/php/lib/DataFile/AvroDataIOReader.php b/lang/php/lib/DataFile/AvroDataIOReader.php index 0dc4dd0ee45..3dd85ff06eb 100644 --- a/lang/php/lib/DataFile/AvroDataIOReader.php +++ b/lang/php/lib/DataFile/AvroDataIOReader.php @@ -25,6 +25,7 @@ use Apache\Avro\AvroUtil; use Apache\Avro\Datum\AvroIOBinaryDecoder; use Apache\Avro\Datum\AvroIODatumReader; +use Apache\Avro\IO\AvroIOException; use Apache\Avro\IO\AvroStringIO; use Apache\Avro\Schema\AvroSchema; @@ -35,35 +36,24 @@ */ class AvroDataIOReader { - /** - * @var string - */ - public $sync_marker; + public string $sync_marker; /** * @var array object container metadata */ - public $metadata; - /** - * @var AvroIO - */ - private $io; - /** - * @var AvroIOBinaryDecoder - */ - private $decoder; - /** - * @var AvroIODatumReader - */ - private $datum_reader; + public array $metadata; + + private AvroIO $io; + + private AvroIOBinaryDecoder $decoder; /** * @var int count of items in block */ - private $block_count; + private int $block_count; /** - * @var compression codec + * @var string compression codec */ - private $codec; + private string $codec; /** * @param AvroIO $io source from which to read @@ -74,16 +64,12 @@ class AvroDataIOReader * is not supported * @uses readHeader() */ - public function __construct($io, $datum_reader) - { - - if (!($io instanceof AvroIO)) { - throw new AvroDataIOException('io must be instance of AvroIO'); - } - + public function __construct( + AvroIO $io, + private AvroIODatumReader $datum_reader + ) { $this->io = $io; $this->decoder = new AvroIOBinaryDecoder($this->io); - $this->datum_reader = $datum_reader; $this->readHeader(); $codec = $this->metadata[AvroDataIO::METADATA_CODEC_ATTR] ?? null; @@ -104,13 +90,13 @@ public function __construct($io, $datum_reader) * Reads header of object container * @throws AvroDataIOException if the file is not an Avro data file. */ - private function readHeader() + private function readHeader(): void { $this->seek(0, AvroIO::SEEK_SET); $magic = $this->read(AvroDataIO::magicSize()); - if (strlen($magic) < AvroDataIO::magicSize()) { + if (strlen((string) $magic) < AvroDataIO::magicSize()) { throw new AvroDataIOException( 'Not an Avro data file: shorter than the Avro magic block' ); @@ -137,7 +123,7 @@ private function readHeader() /** * @uses AvroIO::seek() */ - private function seek($offset, $whence) + private function seek($offset, $whence): bool { return $this->io->seek($offset, $whence); } @@ -145,16 +131,18 @@ private function seek($offset, $whence) /** * @uses AvroIO::read() */ - private function read($len) + private function read($len): string { return $this->io->read($len); } /** + * @return array of data from object container. + * @throws AvroException + * @throws AvroIOException * @internal Would be nice to implement data() as an iterator, I think - * @returns array of data from object container. */ - public function data() + public function data(): array { $data = []; $decoder = $this->decoder; @@ -187,8 +175,8 @@ public function data() throw new AvroException('Please install ext-snappy to use snappy compression.'); } $compressed = $decoder->read($length); - $crc32 = unpack('N', substr($compressed, -4))[1]; - $datum = snappy_uncompress(substr($compressed, 0, -4)); + $crc32 = unpack('N', substr((string) $compressed, -4))[1]; + $datum = snappy_uncompress(substr((string) $compressed, 0, -4)); if ($crc32 === crc32($datum)) { $decoder = new AvroIOBinaryDecoder(new AvroStringIO($datum)); } else { @@ -212,12 +200,12 @@ public function data() /** * @uses AvroIO::isEof() */ - private function isEof() + private function isEof(): bool { return $this->io->isEof(); } - private function skipSync() + private function skipSync(): bool { $proposed_sync_marker = $this->read(AvroDataIO::SYNC_SIZE); if ($proposed_sync_marker != $this->sync_marker) { @@ -232,7 +220,7 @@ private function skipSync() * and the length in bytes of the block) * @returns int length in bytes of the block. */ - private function readBlockHeader() + private function readBlockHeader(): string|int { $this->block_count = $this->decoder->readLong(); return $this->decoder->readLong(); @@ -242,7 +230,7 @@ private function readBlockHeader() * Closes this writer (and its AvroIO object.) * @uses AvroIO::close() */ - public function close() + public function close(): bool { return $this->io->close(); } diff --git a/lang/php/lib/DataFile/AvroDataIOWriter.php b/lang/php/lib/DataFile/AvroDataIOWriter.php index b28143723d7..edfdee91be5 100644 --- a/lang/php/lib/DataFile/AvroDataIOWriter.php +++ b/lang/php/lib/DataFile/AvroDataIOWriter.php @@ -37,59 +37,52 @@ class AvroDataIOWriter /** * @var AvroIO object container where data is written */ - private $io; + private AvroIO $io; /** * @var AvroIOBinaryEncoder encoder for object container */ - private $encoder; - /** - * @var AvroIODatumWriter - */ - private $datum_writer; + private AvroIOBinaryEncoder $encoder; /** * @var AvroStringIO buffer for writing */ - private $buffer; + private AvroStringIO $buffer; + + private AvroIODatumWriter $datum_writer; + /** * @var AvroIOBinaryEncoder encoder for buffer */ - private $buffer_encoder; + private AvroIOBinaryEncoder $buffer_encoder; /** * @var int count of items written to block */ - private $block_count; // AvroIOBinaryEncoder + private int $block_count; /** * @var array map of object container metadata */ - private $metadata; + private array $metadata; /** * @var string compression codec */ - private $codec; + private string $codec; /** * @var string sync marker */ - private $sync_marker; - - /** - * @param AvroIO $io - * @param AvroIODatumWriter $datum_writer - * @param AvroSchema $writers_schema - * @param string $codec - */ - public function __construct($io, $datum_writer, $writers_schema = null, $codec = AvroDataIO::NULL_CODEC) - { - if (!($io instanceof AvroIO)) { - throw new AvroDataIOException('io must be instance of AvroIO'); - } + private string $sync_marker; + public function __construct( + AvroIO $io, + AvroIODatumWriter $datum_writer, + null|string|AvroSchema $writers_schema = null, + string $codec = AvroDataIO::NULL_CODEC + ) { $this->io = $io; - $this->encoder = new AvroIOBinaryEncoder($this->io); $this->datum_writer = $datum_writer; + $this->encoder = new AvroIOBinaryEncoder($this->io); $this->buffer = new AvroStringIO(); $this->buffer_encoder = new AvroIOBinaryEncoder($this->buffer); $this->block_count = 0; - $this->metadata = array(); + $this->metadata = []; if ($writers_schema) { if (!AvroDataIO::isValidCodec($codec)) { diff --git a/lang/php/lib/Datum/AvroIOBinaryDecoder.php b/lang/php/lib/Datum/AvroIOBinaryDecoder.php index 31a007f0ac9..98d3111ec37 100644 --- a/lang/php/lib/Datum/AvroIOBinaryDecoder.php +++ b/lang/php/lib/Datum/AvroIOBinaryDecoder.php @@ -25,6 +25,12 @@ use Apache\Avro\AvroException; use Apache\Avro\AvroGMP; use Apache\Avro\AvroIO; +use Apache\Avro\Schema\AvroArraySchema; +use Apache\Avro\Schema\AvroFixedSchema; +use Apache\Avro\Schema\AvroMapSchema; +use Apache\Avro\Schema\AvroRecordSchema; +use Apache\Avro\Schema\AvroSchema; +use Apache\Avro\Schema\AvroUnionSchema; /** * Decodes and reads Avro data from an AvroIO object encoded using @@ -34,18 +40,13 @@ */ class AvroIOBinaryDecoder { - /** - * @var AvroIO - */ - private $io; - /** * @param AvroIO $io object from which to read. */ - public function __construct($io) - { + public function __construct( + private AvroIO $io + ) { Avro::checkPlatform(); - $this->io = $io; } /** @@ -59,16 +60,16 @@ public function readNull() /** * @returns boolean */ - public function readBoolean() + public function readBoolean(): bool { - return (bool) (1 == ord($this->nextByte())); + return 1 === ord($this->nextByte()); } /** * @returns string the next byte from $this->io. * @throws AvroException if the next byte cannot be read. */ - private function nextByte() + private function nextByte(): string { return $this->read(1); } @@ -77,15 +78,12 @@ private function nextByte() * @param int $len count of bytes to read * @returns string */ - public function read($len) + public function read(int $len): string { return $this->io->read($len); } - /** - * @returns int - */ - public function readInt() + public function readInt(): int { return (int) $this->readLong(); } @@ -93,10 +91,10 @@ public function readInt() /** * @returns string|int */ - public function readLong() + public function readLong(): string|int { $byte = ord($this->nextByte()); - $bytes = array($byte); + $bytes = [$byte]; while (0 != ($byte & 0x80)) { $byte = ord($this->nextByte()); $bytes [] = $byte; @@ -110,11 +108,11 @@ public function readLong() } /** - * @param int[] array of byte ascii values - * @returns long decoded value + * @param int[] $bytes array of byte ascii values + * @return int decoded value * @internal Requires 64-bit platform */ - public static function decodeLongFromArray($bytes) + public static function decodeLongFromArray(array $bytes): int { $b = array_shift($bytes); $n = $b & 0x7f; @@ -127,10 +125,7 @@ public static function decodeLongFromArray($bytes) return ($n >> 1) ^ (($n >> 63) << 63) ^ -($n & 1); } - /** - * @returns float - */ - public function readFloat() + public function readFloat(): float { return self::intBitsToFloat($this->read(4)); } @@ -140,11 +135,8 @@ public function readFloat() * * XXX: This is not endian-aware! See comments in * {@link AvroIOBinaryEncoder::floatToIntBits()} for details. - * - * @param string $bits - * @returns float */ - public static function intBitsToFloat($bits) + public static function intBitsToFloat(string $bits): float { $float = unpack('g', $bits); return (float) $float[1]; @@ -153,7 +145,7 @@ public static function intBitsToFloat($bits) /** * @returns double */ - public function readDouble() + public function readDouble(): float { return self::longBitsToDouble($this->read(8)); } @@ -164,13 +156,11 @@ public function readDouble() * XXX: This is not endian-aware! See comments in * {@link AvroIOBinaryEncoder::floatToIntBits()} for details. * - * @param string $bits - * @returns float */ - public static function longBitsToDouble($bits) + public static function longBitsToDouble(string $bits) { $double = unpack('e', $bits); - return (double) $double[1]; + return (float) $double[1]; } /** @@ -178,7 +168,7 @@ public static function longBitsToDouble($bits) * of UTF-8 encoded character data. * @returns string */ - public function readString() + public function readString(): string { return $this->readBytes(); } @@ -186,47 +176,44 @@ public function readString() /** * @returns string */ - public function readBytes() + public function readBytes(): string { return $this->read($this->readLong()); } - public function skipNull() + public function skipNull(): void { - return null; + return; } - public function skipBoolean() + public function skipBoolean(): void { - return $this->skip(1); + $this->skip(1); } /** * @param int $len count of bytes to skip * @uses AvroIO::seek() */ - public function skip($len) + public function skip(int $len): void { $this->seek($len, AvroIO::SEEK_CUR); } /** - * @param int $offset - * @param int $whence - * @returns boolean true upon success * @uses AvroIO::seek() */ - private function seek($offset, $whence) + private function seek(int $offset, int $whence): bool { return $this->io->seek($offset, $whence); } - public function skipInt() + public function skipInt(): void { - return $this->skipLong(); + $this->skipLong(); } - public function skipLong() + public function skipLong(): void { $b = ord($this->nextByte()); while (0 != ($b & 0x80)) { @@ -234,50 +221,50 @@ public function skipLong() } } - public function skipFloat() + public function skipFloat(): void { - return $this->skip(4); + $this->skip(4); } - public function skipDouble() + public function skipDouble(): void { - return $this->skip(8); + $this->skip(8); } - public function skipString() + public function skipString(): void { - return $this->skipBytes(); + $this->skipBytes(); } - public function skipBytes() + public function skipBytes(): void { - return $this->skip($this->readLong()); + $this->skip($this->readLong()); } - public function skipFixed($writers_schema, AvroIOBinaryDecoder $decoder) + public function skipFixed(AvroFixedSchema $writers_schema, AvroIOBinaryDecoder $decoder): void { $decoder->skip($writers_schema->size()); } - public function skipEnum($writers_schema, AvroIOBinaryDecoder $decoder) + public function skipEnum(AvroSchema $writers_schema, AvroIOBinaryDecoder $decoder): void { $decoder->skipInt(); } - public function skipUnion($writers_schema, AvroIOBinaryDecoder $decoder) + public function skipUnion(AvroUnionSchema $writers_schema, AvroIOBinaryDecoder $decoder): void { $index = $decoder->readLong(); AvroIODatumReader::skipData($writers_schema->schemaByIndex($index), $decoder); } - public function skipRecord($writers_schema, AvroIOBinaryDecoder $decoder) + public function skipRecord(AvroRecordSchema $writers_schema, AvroIOBinaryDecoder $decoder): void { foreach ($writers_schema->fields() as $f) { AvroIODatumReader::skipData($f->type(), $decoder); } } - public function skipArray($writers_schema, AvroIOBinaryDecoder $decoder) + public function skipArray(AvroArraySchema $writers_schema, AvroIOBinaryDecoder $decoder): void { $block_count = $decoder->readLong(); while (0 !== $block_count) { @@ -291,7 +278,7 @@ public function skipArray($writers_schema, AvroIOBinaryDecoder $decoder) } } - public function skipMap($writers_schema, AvroIOBinaryDecoder $decoder) + public function skipMap(AvroMapSchema $writers_schema, AvroIOBinaryDecoder $decoder): void { $block_count = $decoder->readLong(); while (0 !== $block_count) { @@ -307,10 +294,10 @@ public function skipMap($writers_schema, AvroIOBinaryDecoder $decoder) } /** - * @returns int position of pointer in AvroIO instance + * @return int position of pointer in AvroIO instance * @uses AvroIO::tell() */ - private function tell() + private function tell(): int { return $this->io->tell(); } diff --git a/lang/php/lib/Datum/AvroIOBinaryEncoder.php b/lang/php/lib/Datum/AvroIOBinaryEncoder.php index 5f289aafa92..aa14cdc595a 100644 --- a/lang/php/lib/Datum/AvroIOBinaryEncoder.php +++ b/lang/php/lib/Datum/AvroIOBinaryEncoder.php @@ -33,19 +33,14 @@ */ class AvroIOBinaryEncoder { - /** - * @var AvroIO - */ - private $io; - /** * @param AvroIO $io object to which data is to be written. * */ - public function __construct(AvroIO $io) - { + public function __construct( + private readonly AvroIO $io + ) { Avro::checkPlatform(); - $this->io = $io; } /** @@ -56,35 +51,23 @@ public function writeNull($datum): void return; } - /** - * @param boolean $datum - */ - public function writeBoolean($datum) + public function writeBoolean(bool $datum): void { $byte = $datum ? chr(1) : chr(0); $this->write($byte); } - /** - * @param string $datum - */ - public function write($datum): void + public function write(string $datum): void { $this->io->write($datum); } - /** - * @param int $datum - */ - public function writeInt($datum): void + public function writeInt(int|string $datum): void { $this->writeLong($datum); } - /** - * @param int $n - */ - public function writeLong($n): void + public function writeLong(int|string $n): void { if (Avro::usesGmp()) { $this->write(AvroGMP::encodeLong($n)); @@ -152,7 +135,7 @@ public static function floatToIntBits($float): string * @param float $datum * @uses self::doubleToLongBits() */ - public function writeDouble($datum): void + public function writeDouble(float $datum): void { $this->write(self::doubleToLongBits($datum)); } @@ -172,30 +155,26 @@ public static function doubleToLongBits($double): string } /** - * @param string $str * @uses self::writeBytes() */ - public function writeString($str): void + public function writeString(string $str): void { $this->writeBytes($str); } - /** - * @param string $bytes - */ - public function writeBytes($bytes): void + public function writeBytes(string $bytes): void { $this->writeLong(strlen($bytes)); $this->write($bytes); } - public function writeDecimal($decimal, int $scale, int $precision): void + public function writeDecimal(string $decimal, int $scale, int $precision): void { if (!is_numeric($decimal)) { throw new AvroException("Decimal value '{$decimal}' must be numeric"); } - $value = $decimal * (10 ** $scale); + $value = ((float) $decimal) * (10 ** $scale); if (!is_int($value)) { $value = (int) round($value); } diff --git a/lang/php/lib/Datum/AvroIODatumReader.php b/lang/php/lib/Datum/AvroIODatumReader.php index 24278a3dfb2..428886f4818 100644 --- a/lang/php/lib/Datum/AvroIODatumReader.php +++ b/lang/php/lib/Datum/AvroIODatumReader.php @@ -22,9 +22,17 @@ use Apache\Avro\AvroException; use Apache\Avro\Datum\Type\AvroDuration; +use Apache\Avro\Schema\AvroAliasedSchema; +use Apache\Avro\Schema\AvroArraySchema; +use Apache\Avro\Schema\AvroEnumSchema; +use Apache\Avro\Schema\AvroFixedSchema; use Apache\Avro\Schema\AvroLogicalType; +use Apache\Avro\Schema\AvroMapSchema; use Apache\Avro\Schema\AvroName; +use Apache\Avro\Schema\AvroRecordSchema; use Apache\Avro\Schema\AvroSchema; +use Apache\Avro\Schema\AvroSchemaParseException; +use Apache\Avro\Schema\AvroUnionSchema; /** * Handles schema-specifc reading of data from the decoder. @@ -36,19 +44,10 @@ */ class AvroIODatumReader { - /** - * @var AvroSchema - */ - private $writers_schema; - /** - * @var AvroSchema - */ - private $readers_schema; - - public function __construct(?AvroSchema $writers_schema = null, ?AvroSchema $readers_schema = null) - { - $this->writers_schema = $writers_schema; - $this->readers_schema = $readers_schema; + public function __construct( + private ?AvroSchema $writers_schema = null, + private ?AvroSchema $readers_schema = null + ) { } public function setWritersSchema(AvroSchema $readers_schema): void @@ -56,15 +55,12 @@ public function setWritersSchema(AvroSchema $readers_schema): void $this->writers_schema = $readers_schema; } - /** - * @param AvroIOBinaryDecoder $decoder - * @returns string - */ - public function read($decoder) + public function read(AvroIOBinaryDecoder $decoder): mixed { if (is_null($this->readers_schema)) { $this->readers_schema = $this->writers_schema; } + return $this->readData( $this->writers_schema, $this->readers_schema, @@ -72,14 +68,15 @@ public function read($decoder) ); } - /** - * @returns mixed - */ - public function readData(AvroSchema $writers_schema, AvroSchema $readers_schema, AvroIOBinaryDecoder $decoder) - { + public function readData( + AvroSchema $writers_schema, + AvroSchema $readers_schema, + AvroIOBinaryDecoder $decoder + ): mixed { // Schema resolution: reader's schema is a union, writer's schema is not if ( - AvroSchema::UNION_SCHEMA === $readers_schema->type() + $readers_schema instanceof AvroUnionSchema + && AvroSchema::UNION_SCHEMA === $readers_schema->type() && AvroSchema::UNION_SCHEMA !== $writers_schema->type() ) { foreach ($readers_schema->schemas() as $schema) { @@ -90,53 +87,39 @@ public function readData(AvroSchema $writers_schema, AvroSchema $readers_schema, throw new AvroIOSchemaMatchException($writers_schema, $readers_schema); } - switch ($writers_schema->type()) { - case AvroSchema::NULL_TYPE: - return $decoder->readNull(); - case AvroSchema::BOOLEAN_TYPE: - return $decoder->readBoolean(); - case AvroSchema::INT_TYPE: - return $decoder->readInt(); - case AvroSchema::LONG_TYPE: - return $decoder->readLong(); - case AvroSchema::FLOAT_TYPE: - return $decoder->readFloat(); - case AvroSchema::DOUBLE_TYPE: - return $decoder->readDouble(); - case AvroSchema::STRING_TYPE: - return $decoder->readString(); - case AvroSchema::BYTES_TYPE: - $bytes = $decoder->readBytes(); - return $this->readBytes($writers_schema, $readers_schema, $bytes); - case AvroSchema::ARRAY_SCHEMA: - return $this->readArray($writers_schema, $readers_schema, $decoder); - case AvroSchema::MAP_SCHEMA: - return $this->readMap($writers_schema, $readers_schema, $decoder); - case AvroSchema::UNION_SCHEMA: - return $this->readUnion($writers_schema, $readers_schema, $decoder); - case AvroSchema::ENUM_SCHEMA: - return $this->readEnum($writers_schema, $readers_schema, $decoder); - case AvroSchema::FIXED_SCHEMA: - return $this->readFixed($writers_schema, $readers_schema, $decoder); - case AvroSchema::RECORD_SCHEMA: - case AvroSchema::ERROR_SCHEMA: - case AvroSchema::REQUEST_SCHEMA: - return $this->readRecord($writers_schema, $readers_schema, $decoder); - default: - throw new AvroException(sprintf( - "Cannot read unknown schema type: %s", - $writers_schema->type() - )); - } + return match ($writers_schema->type()) { + AvroSchema::NULL_TYPE => $decoder->readNull(), + AvroSchema::BOOLEAN_TYPE => $decoder->readBoolean(), + AvroSchema::INT_TYPE => $decoder->readInt(), + AvroSchema::LONG_TYPE => $decoder->readLong(), + AvroSchema::FLOAT_TYPE => $decoder->readFloat(), + AvroSchema::DOUBLE_TYPE => $decoder->readDouble(), + AvroSchema::STRING_TYPE => $decoder->readString(), + AvroSchema::BYTES_TYPE => $this->readBytes($writers_schema, $readers_schema, $decoder->readBytes()), + AvroSchema::ARRAY_SCHEMA => $this->readArray($writers_schema, $readers_schema, $decoder), + AvroSchema::MAP_SCHEMA => $this->readMap($writers_schema, $readers_schema, $decoder), + AvroSchema::UNION_SCHEMA => $this->readUnion($writers_schema, $readers_schema, $decoder), + AvroSchema::ENUM_SCHEMA => $this->readEnum($writers_schema, $readers_schema, $decoder), + AvroSchema::FIXED_SCHEMA => $this->readFixed($writers_schema, $readers_schema, $decoder), + AvroSchema::RECORD_SCHEMA, + AvroSchema::ERROR_SCHEMA, + AvroSchema::REQUEST_SCHEMA => $this->readRecord($writers_schema, $readers_schema, $decoder), + default => throw new AvroException(sprintf( + "Cannot read unknown schema type: %s", + $writers_schema->type() + )), + }; } /** - * - * @returns boolean true if the schemas are consistent with + * @return bool true if the schemas are consistent with * each other and false otherwise. + * @throws AvroSchemaParseException */ - public static function schemasMatch(AvroSchema $writers_schema, AvroSchema $readers_schema) - { + public static function schemasMatch( + AvroSchema $writers_schema, + AvroSchema $readers_schema + ): bool { $writers_schema_type = $writers_schema->type; $readers_schema_type = $readers_schema->type; @@ -150,12 +133,24 @@ public static function schemasMatch(AvroSchema $writers_schema, AvroSchema $read switch ($readers_schema_type) { case AvroSchema::MAP_SCHEMA: + if ( + !$writers_schema instanceof AvroMapSchema + || !$readers_schema instanceof AvroMapSchema + ) { + return false; + } return self::attributesMatch( $writers_schema->values(), $readers_schema->values(), [AvroSchema::TYPE_ATTR] ); case AvroSchema::ARRAY_SCHEMA: + if ( + !$writers_schema instanceof AvroArraySchema + || !$readers_schema instanceof AvroArraySchema + ) { + return false; + } return self::attributesMatch( $writers_schema->items(), $readers_schema->items(), @@ -219,28 +214,37 @@ public static function schemasMatch(AvroSchema $writers_schema, AvroSchema $read * @param AvroSchema $schema_two * @param string[] $attribute_names array of string attribute names to compare * - * @return boolean true if the attributes match and false otherwise. + * @return bool true if the attributes match and false otherwise. + * @throws AvroSchemaParseException */ - public static function attributesMatch($schema_one, $schema_two, $attribute_names) - { + public static function attributesMatch( + AvroSchema $schema_one, + AvroSchema $schema_two, + array $attribute_names + ): bool { foreach ($attribute_names as $attribute_name) { - if ($schema_one->attribute($attribute_name) !== $schema_two->attribute($attribute_name)) { - if ($attribute_name === AvroSchema::FULLNAME_ATTR) { - foreach ($schema_two->getAliases() as $alias) { - if ( - $schema_one->attribute($attribute_name) === (new AvroName( - $alias, - $schema_two->attribute(AvroSchema::NAMESPACE_ATTR), - null - ))->fullname() - ) { - return true; - } + if ($schema_one->attribute($attribute_name) === $schema_two->attribute($attribute_name)) { + continue; + } + + if (AvroSchema::FULLNAME_ATTR === $attribute_name) { + if (!$schema_two instanceof AvroAliasedSchema) { + return false; + } + foreach ($schema_two->getAliases() as $alias) { + if ( + $schema_one->attribute($attribute_name) !== (new AvroName( + $alias, + $schema_two->attribute(AvroSchema::NAMESPACE_ATTR), + null + ))->fullname() + ) { + return false; } } - return false; } } + return true; } @@ -262,17 +266,17 @@ public function readBytes(AvroSchema $writers_schema, AvroSchema $readers_schema return $bytes; } - /** - * @return array - */ - public function readArray(AvroSchema $writers_schema, AvroSchema $readers_schema, AvroIOBinaryDecoder $decoder) - { - $items = array(); + public function readArray( + AvroArraySchema $writers_schema, + AvroArraySchema $readers_schema, + AvroIOBinaryDecoder $decoder + ): array { + $items = []; $block_count = $decoder->readLong(); while (0 !== $block_count) { if ($block_count < 0) { $block_count = -$block_count; - $block_size = $decoder->readLong(); // Read (and ignore) block size + $decoder->readLong(); // Read (and ignore) block size } for ($i = 0; $i < $block_count; $i++) { $items [] = $this->readData( @@ -283,21 +287,25 @@ public function readArray(AvroSchema $writers_schema, AvroSchema $readers_schema } $block_count = $decoder->readLong(); } + return $items; } /** - * @returns array + * @returns array */ - public function readMap(AvroSchema $writers_schema, AvroSchema $readers_schema, AvroIOBinaryDecoder $decoder) - { - $items = array(); + public function readMap( + AvroMapSchema $writers_schema, + AvroMapSchema $readers_schema, + AvroIOBinaryDecoder $decoder + ): array { + $items = []; $pair_count = $decoder->readLong(); while (0 != $pair_count) { if ($pair_count < 0) { $pair_count = -$pair_count; // Note: we're not doing anything with block_size other than skipping it - $block_size = $decoder->readLong(); + $decoder->readLong(); } for ($i = 0; $i < $pair_count; $i++) { @@ -313,34 +321,36 @@ public function readMap(AvroSchema $writers_schema, AvroSchema $readers_schema, return $items; } - /** - * @returns mixed - */ - public function readUnion(AvroSchema $writers_schema, AvroSchema $readers_schema, AvroIOBinaryDecoder $decoder) - { + public function readUnion( + AvroUnionSchema $writers_schema, + AvroUnionSchema $readers_schema, + AvroIOBinaryDecoder $decoder + ): mixed { $schema_index = $decoder->readLong(); $selected_writers_schema = $writers_schema->schemaByIndex($schema_index); return $this->readData($selected_writers_schema, $readers_schema, $decoder); } - /** - * @returns string - */ - public function readEnum(AvroSchema $writers_schema, AvroSchema $readers_schema, AvroIOBinaryDecoder $decoder) - { + public function readEnum( + AvroEnumSchema $writers_schema, + AvroEnumSchema $readers_schema, + AvroIOBinaryDecoder $decoder + ): ?string { $symbol_index = $decoder->readInt(); $symbol = $writers_schema->symbolByIndex($symbol_index); + if (!$readers_schema->hasSymbol($symbol)) { - null; + return null; } // FIXME: unset wrt schema resolution + return $symbol; } - /** - * @returns string|AvroDuration - */ - public function readFixed(AvroSchema $writers_schema, AvroSchema $readers_schema, AvroIOBinaryDecoder $decoder) - { + public function readFixed( + AvroFixedSchema $writers_schema, + AvroFixedSchema $readers_schema, + AvroIOBinaryDecoder $decoder + ): string|AvroDuration { $logicalTypeWriters = $writers_schema->logicalType(); if ($logicalTypeWriters instanceof AvroLogicalType) { if ($logicalTypeWriters !== $readers_schema->logicalType()) { @@ -358,6 +368,8 @@ public function readFixed(AvroSchema $writers_schema, AvroSchema $readers_schema } return AvroDuration::fromBytes($encodedDuration); + default: + throw new AvroException('Unknown logical type for fixed: ' . $logicalTypeWriters->name()); } } @@ -367,8 +379,11 @@ public function readFixed(AvroSchema $writers_schema, AvroSchema $readers_schema /** * @returns array */ - public function readRecord(AvroSchema $writers_schema, AvroSchema $readers_schema, AvroIOBinaryDecoder $decoder) - { + public function readRecord( + AvroRecordSchema $writers_schema, + AvroRecordSchema $readers_schema, + AvroIOBinaryDecoder $decoder + ) { $readers_fields = $readers_schema->fieldsHash(); $record = []; foreach ($writers_schema->fields() as $writers_field) { @@ -392,66 +407,45 @@ public function readRecord(AvroSchema $writers_schema, AvroSchema $readers_schem } if ($field->hasDefaultValue()) { $record[$field->name()] = $this->readDefaultValue($field->type(), $field->defaultValue()); - } else { - null; } } return $record; } - /** - * @param AvroSchema $writers_schema - * @param AvroIOBinaryDecoder $decoder - */ - public static function skipData($writers_schema, $decoder) - { - switch ($writers_schema->type()) { - case AvroSchema::NULL_TYPE: - return $decoder->skipNull(); - case AvroSchema::BOOLEAN_TYPE: - return $decoder->skipBoolean(); - case AvroSchema::INT_TYPE: - return $decoder->skipInt(); - case AvroSchema::LONG_TYPE: - return $decoder->skipLong(); - case AvroSchema::FLOAT_TYPE: - return $decoder->skipFloat(); - case AvroSchema::DOUBLE_TYPE: - return $decoder->skipDouble(); - case AvroSchema::STRING_TYPE: - return $decoder->skipString(); - case AvroSchema::BYTES_TYPE: - return $decoder->skipBytes(); - case AvroSchema::ARRAY_SCHEMA: - return $decoder->skipArray($writers_schema, $decoder); - case AvroSchema::MAP_SCHEMA: - return $decoder->skipMap($writers_schema, $decoder); - case AvroSchema::UNION_SCHEMA: - return $decoder->skipUnion($writers_schema, $decoder); - case AvroSchema::ENUM_SCHEMA: - return $decoder->skipEnum($writers_schema, $decoder); - case AvroSchema::FIXED_SCHEMA: - return $decoder->skipFixed($writers_schema, $decoder); - case AvroSchema::RECORD_SCHEMA: - case AvroSchema::ERROR_SCHEMA: - case AvroSchema::REQUEST_SCHEMA: - return $decoder->skipRecord($writers_schema, $decoder); - default: - throw new AvroException(sprintf( - 'Unknown schema type: %s', - $writers_schema->type() - )); - } + public static function skipData( + AvroSchema|AvroFixedSchema|AvroEnumSchema|AvroUnionSchema|AvroArraySchema|AvroMapSchema $writers_schema, + AvroIOBinaryDecoder $decoder + ): void { + match ($writers_schema->type()) { + AvroSchema::NULL_TYPE => $decoder->skipNull(), + AvroSchema::BOOLEAN_TYPE => $decoder->skipBoolean(), + AvroSchema::INT_TYPE => $decoder->skipInt(), + AvroSchema::LONG_TYPE => $decoder->skipLong(), + AvroSchema::FLOAT_TYPE => $decoder->skipFloat(), + AvroSchema::DOUBLE_TYPE => $decoder->skipDouble(), + AvroSchema::STRING_TYPE => $decoder->skipString(), + AvroSchema::BYTES_TYPE => $decoder->skipBytes(), + AvroSchema::ARRAY_SCHEMA => $decoder->skipArray($writers_schema, $decoder), + AvroSchema::MAP_SCHEMA => $decoder->skipMap($writers_schema, $decoder), + AvroSchema::UNION_SCHEMA => $decoder->skipUnion($writers_schema, $decoder), + AvroSchema::ENUM_SCHEMA => $decoder->skipEnum($writers_schema, $decoder), + AvroSchema::FIXED_SCHEMA => $decoder->skipFixed($writers_schema, $decoder), + AvroSchema::RECORD_SCHEMA, AvroSchema::ERROR_SCHEMA, AvroSchema::REQUEST_SCHEMA => $decoder->skipRecord($writers_schema, $decoder), + default => throw new AvroException(sprintf( + 'Unknown schema type: %s', + $writers_schema->type() + )), + }; } /** * @param null|boolean|int|float|string|array $default_value - * @returns null|boolean|int|float|string|array + * @return null|boolean|int|float|string|array * * @throws AvroException if $field_schema type is unknown. */ - public function readDefaultValue(AvroSchema $field_schema, $default_value) + public function readDefaultValue(AvroSchema $field_schema, mixed $default_value): mixed { switch ($field_schema->type()) { case AvroSchema::NULL_TYPE: @@ -468,16 +462,18 @@ public function readDefaultValue(AvroSchema $field_schema, $default_value) case AvroSchema::BYTES_TYPE: return $this->readBytes($field_schema, $field_schema, $default_value); case AvroSchema::ARRAY_SCHEMA: - $array = array(); + $array = []; foreach ($default_value as $json_val) { + /** @phpstan-ignore method.notFound */ $val = $this->readDefaultValue($field_schema->items(), $json_val); $array [] = $val; } return $array; case AvroSchema::MAP_SCHEMA: - $map = array(); + $map = []; foreach ($default_value as $key => $json_val) { $map[$key] = $this->readDefaultValue( + /** @phpstan-ignore method.notFound */ $field_schema->values(), $json_val ); @@ -485,6 +481,7 @@ public function readDefaultValue(AvroSchema $field_schema, $default_value) return $map; case AvroSchema::UNION_SCHEMA: return $this->readDefaultValue( + /** @phpstan-ignore method.notFound */ $field_schema->schemaByIndex(0), $default_value ); @@ -492,7 +489,8 @@ public function readDefaultValue(AvroSchema $field_schema, $default_value) case AvroSchema::FIXED_SCHEMA: return $default_value; case AvroSchema::RECORD_SCHEMA: - $record = array(); + $record = []; + /** @phpstan-ignore method.notFound */ foreach ($field_schema->fields() as $field) { $field_name = $field->name(); if (!$json_val = $default_value[$field_name]) { diff --git a/lang/php/lib/Datum/AvroIODatumWriter.php b/lang/php/lib/Datum/AvroIODatumWriter.php index 13e2bbe3887..638a85d5126 100644 --- a/lang/php/lib/Datum/AvroIODatumWriter.php +++ b/lang/php/lib/Datum/AvroIODatumWriter.php @@ -22,9 +22,14 @@ use Apache\Avro\AvroException; use Apache\Avro\Datum\Type\AvroDuration; +use Apache\Avro\Schema\AvroArraySchema; +use Apache\Avro\Schema\AvroEnumSchema; use Apache\Avro\Schema\AvroLogicalType; +use Apache\Avro\Schema\AvroMapSchema; +use Apache\Avro\Schema\AvroRecordSchema; use Apache\Avro\Schema\AvroSchema; use Apache\Avro\Schema\AvroSchemaParseException; +use Apache\Avro\Schema\AvroUnionSchema; /** * Handles schema-specific writing of data to the encoder. @@ -59,7 +64,6 @@ public function write($datum, AvroIOBinaryEncoder $encoder) * @param AvroSchema $writers_schema * @param $datum * @param AvroIOBinaryEncoder $encoder - * @return mixed * * @throws AvroIOTypeException if $datum is invalid for $writers_schema * @throws AvroException if the type is invalid @@ -136,7 +140,7 @@ private function writeValidatedData(AvroSchema $writers_schema, $datum, AvroIOBi } } - private function writeBytes(AvroSchema $writers_schema, $datum, AvroIOBinaryEncoder $encoder): void + private function writeBytes(AvroSchema $writers_schema, string $datum, AvroIOBinaryEncoder $encoder): void { $logicalType = $writers_schema->logicalType(); if ( @@ -154,10 +158,9 @@ private function writeBytes(AvroSchema $writers_schema, $datum, AvroIOBinaryEnco } /** - * @param null|boolean|int|float|string|array $datum item to be written * @throws AvroIOTypeException */ - private function writeArray(AvroSchema $writers_schema, $datum, AvroIOBinaryEncoder $encoder): void + private function writeArray(AvroArraySchema $writers_schema, array $datum, AvroIOBinaryEncoder $encoder): void { $datum_count = count($datum); if (0 < $datum_count) { @@ -171,10 +174,9 @@ private function writeArray(AvroSchema $writers_schema, $datum, AvroIOBinaryEnco } /** - * @param $datum * @throws AvroIOTypeException */ - private function writeMap(AvroSchema $writers_schema, $datum, AvroIOBinaryEncoder $encoder): void + private function writeMap(AvroMapSchema $writers_schema, array $datum, AvroIOBinaryEncoder $encoder): void { $datum_count = count($datum); if ($datum_count > 0) { @@ -187,8 +189,11 @@ private function writeMap(AvroSchema $writers_schema, $datum, AvroIOBinaryEncode $encoder->writeLong(0); } - private function writeFixed(AvroSchema $writers_schema, $datum, AvroIOBinaryEncoder $encoder): void - { + private function writeFixed( + AvroSchema $writers_schema, + string|AvroDuration $datum, + AvroIOBinaryEncoder $encoder + ): void { $logicalType = $writers_schema->logicalType(); if ( $logicalType instanceof AvroLogicalType @@ -222,13 +227,13 @@ private function writeFixed(AvroSchema $writers_schema, $datum, AvroIOBinaryEnco $encoder->write($datum); } - private function writeEnum(AvroSchema $writers_schema, $datum, AvroIOBinaryEncoder $encoder): void + private function writeEnum(AvroEnumSchema $writers_schema, $datum, AvroIOBinaryEncoder $encoder): void { $datum_index = $writers_schema->symbolIndex($datum); $encoder->writeInt($datum_index); } - private function writeRecord(AvroSchema $writers_schema, $datum, AvroIOBinaryEncoder $encoder): void + private function writeRecord(AvroRecordSchema $writers_schema, mixed $datum, AvroIOBinaryEncoder $encoder): void { foreach ($writers_schema->fields() as $field) { $this->writeValidatedData($field->type(), $datum[$field->name()] ?? null, $encoder); @@ -239,7 +244,7 @@ private function writeRecord(AvroSchema $writers_schema, $datum, AvroIOBinaryEnc * @throws AvroIOTypeException * @throws AvroSchemaParseException */ - private function writeUnion(AvroSchema $writers_schema, $datum, AvroIOBinaryEncoder $encoder): void + private function writeUnion(AvroUnionSchema $writers_schema, mixed $datum, AvroIOBinaryEncoder $encoder): void { $datum_schema_index = -1; $datum_schema = null; diff --git a/lang/php/lib/Datum/Type/AvroDuration.php b/lang/php/lib/Datum/Type/AvroDuration.php index 7bf40d4a118..369a8d6a490 100644 --- a/lang/php/lib/Datum/Type/AvroDuration.php +++ b/lang/php/lib/Datum/Type/AvroDuration.php @@ -25,16 +25,13 @@ use Apache\Avro\AvroException; use Apache\Avro\Schema\AvroSchema; -class AvroDuration +class AvroDuration implements \Stringable { - /** @var int */ - private $months; + private readonly int $months; - /** @var int */ - private $days; + private readonly int $days; - /** @var int */ - private $milliseconds; + private readonly int $milliseconds; /** * @throws AvroException diff --git a/lang/php/lib/IO/AvroFile.php b/lang/php/lib/IO/AvroFile.php index b68de63eed1..687896b0ed9 100644 --- a/lang/php/lib/IO/AvroFile.php +++ b/lang/php/lib/IO/AvroFile.php @@ -18,6 +18,8 @@ * limitations under the License. */ +declare(strict_types=1); + namespace Apache\Avro\IO; use Apache\Avro\AvroIO; @@ -26,7 +28,7 @@ * AvroIO wrapper for PHP file access functions * @package Avro */ -class AvroFile extends AvroIO +class AvroFile implements AvroIO { /** * @var string fopen read mode value. Used internally. @@ -38,33 +40,25 @@ class AvroFile extends AvroIO */ public const FOPEN_WRITE_MODE = 'wb'; - /** - * @var string - */ - private $file_path; - /** * @var resource file handle for AvroFile instance */ private $file_handle; - public function __construct($file_path, $mode = self::READ_MODE) - { - /** - * XXX: should we check for file existence (in case of reading) - * or anything else about the provided file_path argument? - */ - $this->file_path = $file_path; + public function __construct( + private string $file_path, + string $mode = self::READ_MODE + ) { switch ($mode) { case self::WRITE_MODE: $this->file_handle = fopen($this->file_path, self::FOPEN_WRITE_MODE); - if (false == $this->file_handle) { + if (false === $this->file_handle) { throw new AvroIOException('Could not open file for writing'); } break; case self::READ_MODE: $this->file_handle = fopen($this->file_path, self::FOPEN_READ_MODE); - if (false == $this->file_handle) { + if (false === $this->file_handle) { throw new AvroIOException('Could not open file for reading'); } break; @@ -81,12 +75,12 @@ public function __construct($file_path, $mode = self::READ_MODE) } /** - * @returns int count of bytes written + * @return int count of bytes written * @throws AvroIOException if write failed. */ - public function write($str) + public function write(string $bytes): int { - $len = fwrite($this->file_handle, $str); + $len = fwrite($this->file_handle, $bytes); if (false === $len) { throw new AvroIOException(sprintf('Could not write to file')); } @@ -97,7 +91,7 @@ public function write($str) * @returns int current position within the file * @throws AvroIOException if tell failed. */ - public function tell() + public function tell(): int { $position = ftell($this->file_handle); if (false === $position) { @@ -108,10 +102,10 @@ public function tell() /** * Closes the file. - * @returns boolean true if successful. + * @returns bool true if successful. * @throws AvroIOException if there was an error closing the file. */ - public function close() + public function close(): bool { $res = fclose($this->file_handle); if (false === $res) { @@ -125,7 +119,7 @@ public function close() * and false otherwise. * @see AvroIO::isEof() as behavior differs from feof() */ - public function isEof() + public function isEof(): bool { $this->read(1); if (feof($this->file_handle)) { @@ -140,7 +134,7 @@ public function isEof() * @returns string bytes read * @throws AvroIOException if length value is negative or if the read failed */ - public function read($len) + public function read(int $len): string { if (0 > $len) { throw new AvroIOException( @@ -148,7 +142,7 @@ public function read($len) ); } - if (0 == $len) { + if (0 === $len) { return ''; } @@ -166,7 +160,7 @@ public function read($len) * @throws AvroIOException if seek failed. * @see AvroIO::seek() */ - public function seek($offset, $whence = SEEK_SET): bool + public function seek(int $offset, int $whence = SEEK_SET): bool { $res = fseek($this->file_handle, $offset, $whence); // Note: does not catch seeking beyond end of file @@ -186,7 +180,7 @@ public function seek($offset, $whence = SEEK_SET): bool * @returns boolean true if the flush was successful. * @throws AvroIOException if there was an error flushing the file. */ - public function flush() + public function flush(): bool { $res = fflush($this->file_handle); if (false === $res) { diff --git a/lang/php/lib/IO/AvroStringIO.php b/lang/php/lib/IO/AvroStringIO.php index 9b98ff31c2e..9e3288bd6fb 100644 --- a/lang/php/lib/IO/AvroStringIO.php +++ b/lang/php/lib/IO/AvroStringIO.php @@ -18,6 +18,8 @@ * limitations under the License. */ +declare(strict_types=1); + namespace Apache\Avro\IO; use Apache\Avro\AvroIO; @@ -26,20 +28,20 @@ * AvroIO wrapper for string access * @package Avro */ -class AvroStringIO extends AvroIO +class AvroStringIO implements AvroIO, \Stringable { /** * @var string */ - private $string_buffer; + private string $string_buffer; /** * @var int current position in string */ - private $current_index; + private int $current_index; /** - * @var boolean whether or not the string is closed. + * @var bool whether or not the string is closed. */ - private $is_closed; + private bool $is_closed; /** * @param string $str initial value of AvroStringIO buffer. Regardless @@ -47,7 +49,7 @@ class AvroStringIO extends AvroIO * beginning of the buffer. * @throws AvroIOException if a non-string value is passed as $str */ - public function __construct($str = '') + public function __construct(string $str = '') { $this->is_closed = false; $this->string_buffer = ''; @@ -65,21 +67,21 @@ public function __construct($str = '') /** * Append bytes to this buffer. * (Nothing more is needed to support Avro.) - * @param string $arg bytes to write + * @param string $bytes bytes to write * @returns int count of bytes written. * @throws AvroIOException if $args is not a string value. */ - public function write($arg) + public function write(string $bytes): int { $this->checkClosed(); - if (is_string($arg)) { - return $this->appendStr($arg); + if (is_string($bytes)) { + return $this->appendStr($bytes); } throw new AvroIOException( sprintf( 'write argument must be a string: (%s) %s', - gettype($arg), - var_export($arg, true) + gettype($bytes), + var_export($bytes, true) ) ); } @@ -87,7 +89,7 @@ public function write($arg) /** * @throws AvroIOException if the buffer is closed. */ - private function checkClosed() + private function checkClosed(): void { if ($this->isClosed()) { throw new AvroIOException('Buffer is closed'); @@ -95,10 +97,9 @@ private function checkClosed() } /** - * @returns boolean true if this buffer is closed and false - * otherwise. + * @return bool true if this buffer is closed and false otherwise. */ - public function isClosed() + public function isClosed(): bool { return $this->is_closed; } @@ -106,9 +107,10 @@ public function isClosed() /** * Appends bytes to this buffer. * @param string $str - * @returns integer count of bytes written. + * @return int count of bytes written. + * @throws AvroIOException */ - private function appendStr($str) + private function appendStr(string $str): int { $this->checkClosed(); $this->string_buffer .= $str; @@ -121,7 +123,7 @@ private function appendStr($str) * @returns string bytes read from buffer * @todo test for fencepost errors wrt updating current_index */ - public function read($len) + public function read($len): string { $this->checkClosed(); $read = ''; @@ -141,7 +143,7 @@ public function read($len) * @internal Could probably memoize length for performance, but * no need do this yet. */ - public function length() + public function length(): int { return strlen($this->string_buffer); } @@ -183,37 +185,34 @@ public function seek($offset, $whence = self::SEEK_SET): bool } /** - * @returns int * @see AvroIO::tell() */ - public function tell() + public function tell(): int { return $this->current_index; } /** - * @returns boolean * @see AvroIO::isEof() */ - public function isEof() + public function isEof(): bool { return ($this->current_index >= $this->length()); } /** * No-op provided for compatibility with AvroIO interface. - * @returns boolean true + * @returns bool true */ - public function flush() + public function flush(): bool { return true; } /** * Marks this buffer as closed. - * @returns boolean true */ - public function close() + public function close(): bool { $this->checkClosed(); $this->is_closed = true; @@ -223,9 +222,9 @@ public function close() /** * Truncates the truncate buffer to 0 bytes and returns the pointer * to the beginning of the buffer. - * @returns boolean true + * @returns bool true */ - public function truncate() + public function truncate(): bool { $this->checkClosed(); $this->string_buffer = ''; @@ -237,7 +236,7 @@ public function truncate() * @returns string * @uses self::__toString() */ - public function string() + public function string(): string { return (string) $this; } @@ -245,7 +244,7 @@ public function string() /** * @returns string */ - public function __toString() + public function __toString(): string { return $this->string_buffer; } diff --git a/lang/php/lib/Protocol/AvroProtocol.php b/lang/php/lib/Protocol/AvroProtocol.php index a1be7b79bf5..ae0ec3538b2 100644 --- a/lang/php/lib/Protocol/AvroProtocol.php +++ b/lang/php/lib/Protocol/AvroProtocol.php @@ -18,10 +18,13 @@ * limitations under the License. */ +declare(strict_types=1); + namespace Apache\Avro\Protocol; use Apache\Avro\Schema\AvroNamedSchemata; use Apache\Avro\Schema\AvroSchema; +use Apache\Avro\Schema\AvroSchemaParseException; /** * Avro library for protocols @@ -29,39 +32,64 @@ */ class AvroProtocol { - public $protocol; - public $name; - public $namespace; - public $schemata; - public $messages; + public function __construct( + public readonly string $protocol, + public readonly string $name, + public readonly string $namespace, + public readonly AvroNamedSchemata $schemata, + /** @var array */ + public readonly array $messages, + ) { + } - public static function parse($json) + /** + * @throws AvroProtocolParseException + * @throws AvroSchemaParseException + */ + public static function parse(string $json): self { - if (is_null($json)) { - throw new AvroProtocolParseException("Protocol can't be null"); + try { + return self::realParse( + json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR) + ); + } catch (\JsonException $e) { + throw new AvroProtocolParseException( + "Protocol json schema is invalid: ".$e->getMessage(), + previous: $e + ); } - - $protocol = new AvroProtocol(); - $protocol->realParse(json_decode($json, true)); - return $protocol; } - public function realParse($avro) + /** + * @param array $avro AVRO protocol as associative array + * @throws AvroSchemaParseException + */ + public static function realParse(array $avro): self { - $this->protocol = $avro["protocol"]; - $this->namespace = $avro["namespace"]; - $this->schemata = new AvroNamedSchemata(); - $this->name = $avro["protocol"]; + $schemata = new AvroNamedSchemata(); if (!is_null($avro["types"])) { - $types = AvroSchema::realParse($avro["types"], $this->namespace, $this->schemata); + AvroSchema::realParse($avro["types"], $avro["namespace"], $schemata); } + $messages = []; if (!is_null($avro["messages"])) { foreach ($avro["messages"] as $messageName => $messageAvro) { - $message = new AvroProtocolMessage($messageName, $messageAvro, $this); - $this->messages[$messageName] = $message; + $messages[] = new AvroProtocolMessage( + name: $messageName, + avro: $messageAvro, + namespace: $avro["namespace"], + schemata: $schemata + ); } } + + return new self( + protocol: $avro["protocol"], + name: $avro["protocol"], + namespace: $avro["namespace"], + schemata: $schemata, + messages: $messages + ); } } diff --git a/lang/php/lib/Protocol/AvroProtocolMessage.php b/lang/php/lib/Protocol/AvroProtocolMessage.php index 186e3ce5e8f..9677591dd2b 100644 --- a/lang/php/lib/Protocol/AvroProtocolMessage.php +++ b/lang/php/lib/Protocol/AvroProtocolMessage.php @@ -18,44 +18,54 @@ * limitations under the License. */ +declare(strict_types=1); + namespace Apache\Avro\Protocol; use Apache\Avro\Schema\AvroName; +use Apache\Avro\Schema\AvroNamedSchemata; use Apache\Avro\Schema\AvroPrimitiveSchema; use Apache\Avro\Schema\AvroRecordSchema; use Apache\Avro\Schema\AvroSchema; +use Apache\Avro\Schema\AvroSchemaParseException; class AvroProtocolMessage { - public $name; + public readonly AvroRecordSchema $request; + + public readonly ?AvroSchema $response; /** - * @var AvroRecordSchema $request + * @throws AvroSchemaParseException */ - public $request; - - public $response; - - public function __construct($name, $avro, $protocol) - { - $this->name = $name; + public function __construct( + public string $name, + array $avro, + string $namespace, + AvroNamedSchemata $schemata, + ) { $this->request = new AvroRecordSchema( - new AvroName($name, null, $protocol->namespace), - null, - $avro['request'], - $protocol->schemata, - AvroSchema::REQUEST_SCHEMA + name: new AvroName($this->name, null, $namespace), + doc: null, + fields: $avro['request'], + schemata: $schemata, + schema_type: AvroSchema::REQUEST_SCHEMA ); + $response = null; if (array_key_exists('response', $avro)) { - $this->response = $protocol->schemata->schemaByName(new AvroName( - $avro['response'], - $protocol->namespace, - $protocol->namespace - )); - if ($this->response == null) { - $this->response = new AvroPrimitiveSchema($avro['response']); + $response = $schemata->schemaByName( + new AvroName( + name: $avro['response'], + namespace: $namespace, + default_namespace: $namespace + ) + ); + + if (is_null($response)) { + $response = new AvroPrimitiveSchema($avro['response']); } } + $this->response = $response; } } diff --git a/lang/php/lib/Schema/AvroAliasedSchema.php b/lang/php/lib/Schema/AvroAliasedSchema.php new file mode 100644 index 00000000000..13ca0a638d4 --- /dev/null +++ b/lang/php/lib/Schema/AvroAliasedSchema.php @@ -0,0 +1,28 @@ +items; } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { $avro = parent::toAvro(); $avro[AvroSchema::ITEMS_ATTR] = $this->is_items_schema_from_schemata diff --git a/lang/php/lib/Schema/AvroEnumSchema.php b/lang/php/lib/Schema/AvroEnumSchema.php index 523fedd39a5..e9f22268e5e 100644 --- a/lang/php/lib/Schema/AvroEnumSchema.php +++ b/lang/php/lib/Schema/AvroEnumSchema.php @@ -107,10 +107,7 @@ public function symbolIndex($symbol) throw new AvroException(sprintf("Invalid symbol value '%s'", $symbol)); } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { $avro = parent::toAvro(); $avro[AvroSchema::SYMBOLS_ATTR] = $this->symbols; diff --git a/lang/php/lib/Schema/AvroField.php b/lang/php/lib/Schema/AvroField.php index eeb50bc197d..e177eb774ae 100644 --- a/lang/php/lib/Schema/AvroField.php +++ b/lang/php/lib/Schema/AvroField.php @@ -24,7 +24,7 @@ * Field of an {@link AvroRecordSchema} * @package Avro */ -class AvroField extends AvroSchema +class AvroField extends AvroSchema implements AvroAliasedSchema { /** * @var string fields name attribute name @@ -59,66 +59,55 @@ class AvroField extends AvroSchema /** * @var array list of valid field sort order values */ - private static $validFieldSortOrders = array( + private static array $validFieldSortOrders = [ self::ASC_SORT_ORDER, self::DESC_SORT_ORDER, self::IGNORE_SORT_ORDER - ); - /** - * @var string - */ - private $name; + ]; + + private ?string $name; + + private bool $isTypeFromSchemata; + /** - * @var boolean whether or no there is a default value + * @var bool whether or no there is a default value */ - private $hasDefault; + private bool $hasDefault; + /** * @var string field default value */ - private $default; + private mixed $default; /** - * @var string sort order of this field + * @var null|string sort order of this field */ - private $order; - /** - * @var boolean whether or not the AvroNamedSchema of this field is - * defined in the AvroNamedSchemata instance - */ - private $isTypeFromSchemata; + private ?string $order; /** * @var array|null */ - private $aliases; + private ?array $aliases; /** - * @param string $name - * @param AvroSchema $schema - * @param boolean $is_type_from_schemata - * @param $has_default - * @param string $default - * @param string $order - * @param array $aliases * @throws AvroSchemaParseException * @todo Check validity of $default value - * @todo Check validity of $order value */ public function __construct( - $name, - $schema, - $is_type_from_schemata, - $has_default, - $default, - $order = null, - $aliases = null + ?string $name, + string|AvroSchema $schema, + bool $isTypeFromSchemata, + bool $hasDefault, + mixed $default, + ?string $order = null, + mixed $aliases = null ) { if (!AvroName::isWellFormedName($name)) { throw new AvroSchemaParseException('Field requires a "name" attribute'); } parent::__construct($schema); - $this->isTypeFromSchemata = $is_type_from_schemata; $this->name = $name; - $this->hasDefault = $has_default; + $this->isTypeFromSchemata = $isTypeFromSchemata; + $this->hasDefault = $hasDefault; if ($this->hasDefault) { $this->default = $default; } @@ -129,11 +118,10 @@ public function __construct( } /** - * @param string $order * @throws AvroSchemaParseException if $order is not a valid * field order value. */ - private static function checkOrderValue($order) + private static function checkOrderValue(?string $order): void { if (!is_null($order) && !self::isValidFieldSortOrder($order)) { throw new AvroSchemaParseException( @@ -142,31 +130,27 @@ private static function checkOrderValue($order) } } - /** - * @param string $order - * @returns boolean - */ - private static function isValidFieldSortOrder($order) + private static function isValidFieldSortOrder(string $order): bool { - return in_array($order, self::$validFieldSortOrders); + return in_array($order, self::$validFieldSortOrders, true); } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { - $avro = array(AvroField::FIELD_NAME_ATTR => $this->name); + $avro = [self::FIELD_NAME_ATTR => $this->name]; - $avro[AvroSchema::TYPE_ATTR] = ($this->isTypeFromSchemata) - ? $this->type->qualifiedName() : $this->type->toAvro(); + $avro[AvroSchema::TYPE_ATTR] = match (true) { + $this->isTypeFromSchemata && $this->type instanceof AvroNamedSchema => $this->type->qualifiedName(), + $this->type instanceof AvroSchema => $this->type->toAvro(), + is_string($this->type) => $this->type, + }; if (isset($this->default)) { - $avro[AvroField::DEFAULT_ATTR] = $this->default; + $avro[self::DEFAULT_ATTR] = $this->default; } if ($this->order) { - $avro[AvroField::ORDER_ATTR] = $this->order; + $avro[self::ORDER_ATTR] = $this->order; } return $avro; @@ -196,7 +180,7 @@ public function hasDefaultValue() return $this->hasDefault; } - public function getAliases() + public function getAliases(): ?array { return $this->aliases; } diff --git a/lang/php/lib/Schema/AvroFixedSchema.php b/lang/php/lib/Schema/AvroFixedSchema.php index a48073ad361..366a83a60eb 100644 --- a/lang/php/lib/Schema/AvroFixedSchema.php +++ b/lang/php/lib/Schema/AvroFixedSchema.php @@ -34,36 +34,24 @@ class AvroFixedSchema extends AvroNamedSchema private $size; /** - * @param AvroName $name - * @param string $doc Set to null, as fixed schemas don't have doc strings * @param int $size byte count of this fixed schema data value - * @param AvroNamedSchemata &$schemata - * @param array $aliases * @throws AvroSchemaParseException */ - public function __construct($name, $doc, $size, &$schemata = null, $aliases = null) + public function __construct(AvroName $name, int $size, ?AvroNamedSchemata &$schemata = null, ?array $aliases = null) { - if (!is_int($size)) { - throw new AvroSchemaParseException( - 'Fixed Schema requires a valid integer for "size" attribute' - ); - } parent::__construct(AvroSchema::FIXED_SCHEMA, $name, null, $schemata, $aliases); $this->size = $size; } /** - * @returns int byte count of this fixed schema data value + * @return int byte count of this fixed schema data value */ - public function size() + public function size(): int { return $this->size; } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { $avro = parent::toAvro(); $avro[AvroSchema::SIZE_ATTR] = $this->size; @@ -76,11 +64,10 @@ public function toAvro() */ public static function duration( AvroName $name, - ?string $doc, - AvroNamedSchemata &$schemata = null, + ?AvroNamedSchemata &$schemata = null, ?array $aliases = null ): self { - $fixedSchema = new self($name, $doc, 12, $schemata, $aliases); + $fixedSchema = new self($name, 12, $schemata, $aliases); $fixedSchema->logicalType = AvroLogicalType::duration(); @@ -94,14 +81,13 @@ public static function duration( */ public static function decimal( AvroName $name, - ?string $doc, int $size, int $precision, int $scale, ?AvroNamedSchemata &$schemata = null, ?array $aliases = null ): self { - $self = new self($name, $doc, $size, $schemata, $aliases); + $self = new self($name, $size, $schemata, $aliases); $maxPrecision = (int) floor(log10(self::maxDecimalMagnitude($size))); diff --git a/lang/php/lib/Schema/AvroLogicalType.php b/lang/php/lib/Schema/AvroLogicalType.php index 9b19794473a..bb175c608d1 100644 --- a/lang/php/lib/Schema/AvroLogicalType.php +++ b/lang/php/lib/Schema/AvroLogicalType.php @@ -27,16 +27,8 @@ class AvroLogicalType public const ATTRIBUTE_DECIMAL_PRECISION = 'precision'; public const ATTRIBUTE_DECIMAL_SCALE = 'scale'; - /** @var string */ - private $name; - - /** @var array */ - private $attributes; - - public function __construct(string $name, array $attributes = []) + public function __construct(private readonly string $name, private readonly array $attributes = []) { - $this->name = $name; - $this->attributes = $attributes; } public function name(): string @@ -49,7 +41,7 @@ public function attributes(): array return $this->attributes; } - public function toAvro(): array + public function toAvro(): string|array { $avro[AvroSchema::LOGICAL_TYPE_ATTR] = $this->name; return array_merge($avro, $this->attributes); diff --git a/lang/php/lib/Schema/AvroMapSchema.php b/lang/php/lib/Schema/AvroMapSchema.php index dfcb9dab85f..46ca3e0502a 100644 --- a/lang/php/lib/Schema/AvroMapSchema.php +++ b/lang/php/lib/Schema/AvroMapSchema.php @@ -28,24 +28,17 @@ class AvroMapSchema extends AvroSchema { /** - * @var string|AvroSchema named schema name or AvroSchema - * of map schema values. + * AvroSchema definition based on input $values variable that could contain a primitive type or an associative + * array with the AVRO definition. */ - private $values; + private AvroSchema $values; /** - * @var boolean true if the named schema - * XXX Couldn't we derive this based on whether or not - * $this->values is a string? + * @var bool true if the named schema */ - private $isValuesSchemaFromSchemata; + private bool $isValuesSchemaFromSchemata; - /** - * @param string|AvroSchema $values - * @param string $defaultNamespace namespace of enclosing schema - * @param AvroNamedSchemata &$schemata - */ - public function __construct($values, $defaultNamespace, &$schemata = null) + public function __construct(string|array $values, ?string $defaultNamespace, ?AvroNamedSchemata &$schemata = null) { parent::__construct(AvroSchema::MAP_SCHEMA); @@ -69,22 +62,18 @@ public function __construct($values, $defaultNamespace, &$schemata = null) $this->values = $values_schema; } - /** - * @returns XXX|AvroSchema - */ - public function values() + public function values(): AvroSchema { return $this->values; } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { $avro = parent::toAvro(); - $avro[AvroSchema::VALUES_ATTR] = $this->isValuesSchemaFromSchemata - ? $this->values->qualifiedName() : $this->values->toAvro(); + $avro[AvroSchema::VALUES_ATTR] = match (true) { + $this->isValuesSchemaFromSchemata && $this->values instanceof AvroNamedSchema => $this->values->qualifiedName(), + default => $this->values->toAvro(), + }; return $avro; } } diff --git a/lang/php/lib/Schema/AvroName.php b/lang/php/lib/Schema/AvroName.php index 19ddd06972d..ae34fffc573 100644 --- a/lang/php/lib/Schema/AvroName.php +++ b/lang/php/lib/Schema/AvroName.php @@ -23,7 +23,7 @@ /** * @package Avro */ -class AvroName +class AvroName implements \Stringable { /** * @var string character used to separate names comprising the fullname @@ -37,26 +37,27 @@ class AvroName /** * @var string valid names are matched by self::NAME_REGEXP */ - private $name; + private string $name; /** - * @var string + * @var null|string */ - private $namespace; + private ?string $namespace; /** * @var string */ - private $fullname; + private string $fullname; /** * @var string Name qualified as necessary given its default namespace. */ - private $qualified_name; + private string $qualified_name; /** - * @param string $name - * @param string $namespace - * @param string $default_namespace + * @param mixed $name + * @param string|null $namespace + * @param string|null $default_namespace + * @throws AvroSchemaParseException */ - public function __construct($name, $namespace, $default_namespace) + public function __construct(mixed $name, ?string $namespace, ?string $default_namespace) { if (!is_string($name) || empty($name)) { throw new AvroSchemaParseException('Name must be a non-empty string.'); @@ -82,11 +83,11 @@ public function __construct($name, $namespace, $default_namespace) /** * @param string $namespace - * @returns boolean true if namespace is composed of valid names + * @return bool true if namespace is composed of valid names * @throws AvroSchemaParseException if any of the namespace components * are invalid. */ - private static function checkNamespaceNames($namespace) + private static function checkNamespaceNames(string $namespace): bool { foreach (explode(self::NAME_SEPARATOR, $namespace) as $n) { if (empty($n) || (0 === preg_match(self::NAME_REGEXP, $n))) { @@ -102,7 +103,7 @@ private static function checkNamespaceNames($namespace) * @returns string * @throws AvroSchemaParseException if any of the names are not valid. */ - private static function parseFullname($name, $namespace) + private static function parseFullname($name, $namespace): string { if (!is_string($namespace) || empty($namespace)) { throw new AvroSchemaParseException('Namespace must be a non-empty string.'); @@ -112,9 +113,9 @@ private static function parseFullname($name, $namespace) } /** - * @returns string[] array($name, $namespace) + * @return array{0: string, 1: null|string} */ - public static function extractNamespace($name, $namespace = null) + public static function extractNamespace(string $name, ?string $namespace = null): array { $parts = explode(self::NAME_SEPARATOR, $name); if (count($parts) > 1) { @@ -128,26 +129,26 @@ public static function extractNamespace($name, $namespace = null) * @returns boolean true if the given name is well-formed * (is a non-null, non-empty string) and false otherwise */ - public static function isWellFormedName($name) + public static function isWellFormedName($name): bool { return (is_string($name) && !empty($name) && preg_match(self::NAME_REGEXP, $name)); } /** - * @returns array array($name, $namespace) + * @return array{0: string, 1: string} */ - public function nameAndNamespace() + public function nameAndNamespace(): array { return [$this->name, $this->namespace]; } /** - * @returns string fullname + * @return string fullname * @uses $this->fullname() */ - public function __toString() + public function __toString(): string { - return $this->fullname(); + return (string) $this->fullname(); } /** @@ -165,4 +166,9 @@ public function qualifiedName() { return $this->qualified_name; } + + public function namespace(): ?string + { + return $this->namespace; + } } diff --git a/lang/php/lib/Schema/AvroNamedSchema.php b/lang/php/lib/Schema/AvroNamedSchema.php index 7db254dbb7d..6449389cfdb 100644 --- a/lang/php/lib/Schema/AvroNamedSchema.php +++ b/lang/php/lib/Schema/AvroNamedSchema.php @@ -23,45 +23,23 @@ /** * Parent class of named Avro schema * @package Avro - * @todo Refactor AvroNamedSchema to use an AvroName instance - * to store name information. */ -class AvroNamedSchema extends AvroSchema +class AvroNamedSchema extends AvroSchema implements AvroAliasedSchema { /** - * @var AvroName $name - */ - private $name; - - /** - * @var string documentation string - */ - private $doc; - /** - * @var array - */ - private $aliases; - - /** - * @param string $type - * @param AvroName $name - * @param string $doc documentation string - * @param AvroNamedSchemata &$schemata - * @param array $aliases * @throws AvroSchemaParseException */ - public function __construct($type, $name, $doc = null, &$schemata = null, $aliases = null) - { + public function __construct( + string $type, + private readonly AvroName $name, + private readonly ?string $doc = null, + ?AvroNamedSchemata &$schemata = null, + private ?array $aliases = null + ) { parent::__construct($type); - $this->name = $name; - if ($doc && !is_string($doc)) { - throw new AvroSchemaParseException('Schema doc attribute must be a string'); - } - $this->doc = $doc; - if ($aliases) { - self::hasValidAliases($aliases); - $this->aliases = $aliases; + if ($this->aliases) { + self::hasValidAliases($this->aliases); } if (!is_null($schemata)) { @@ -69,15 +47,12 @@ public function __construct($type, $name, $doc = null, &$schemata = null, $alias } } - public function getAliases() + public function getAliases(): ?array { return $this->aliases; } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { $avro = parent::toAvro(); [$name, $namespace] = AvroName::extractNamespace($this->qualifiedName()); @@ -94,16 +69,18 @@ public function toAvro() return $avro; } - public function qualifiedName() + public function qualifiedName(): string { return $this->name->qualifiedName(); } - /** - * @returns string - */ - public function fullname() + public function fullname(): string { return $this->name->fullname(); } + + public function namespace(): ?string + { + return $this->name->namespace(); + } } diff --git a/lang/php/lib/Schema/AvroNamedSchemata.php b/lang/php/lib/Schema/AvroNamedSchemata.php index 1e7a88b2e18..82428dd4fa5 100644 --- a/lang/php/lib/Schema/AvroNamedSchemata.php +++ b/lang/php/lib/Schema/AvroNamedSchemata.php @@ -28,17 +28,13 @@ */ class AvroNamedSchemata { - /** - * @var AvroNamedSchema[] - */ - private $schemata; - - /** - * @param AvroNamedSchemata[] - */ - public function __construct($schemata = array()) + public function __construct( + /** + * @var AvroNamedSchema[] + */ + private $schemata = [] + ) { - $this->schemata = $schemata; } public function listSchemas() @@ -51,33 +47,30 @@ public function listSchemas() /** * @param AvroName $name - * @returns AvroSchema|null + * @return AvroSchema|null */ - public function schemaByName($name) + public function schemaByName(AvroName $name): ?AvroSchema { return $this->schema($name->fullname()); } /** * @param string $fullname - * @returns AvroSchema|null the schema which has the given name, + * @return AvroSchema|null the schema which has the given name, * or null if there is no schema with the given name. */ - public function schema($fullname) + public function schema(string $fullname): ?AvroSchema { - if (isset($this->schemata[$fullname])) { - return $this->schemata[$fullname]; - } - return null; + return $this->schemata[$fullname] ?? null; } /** * Creates a new AvroNamedSchemata instance of this schemata instance * with the given $schema appended. - * @param AvroNamedSchema schema to add to this existing schemata - * @returns AvroNamedSchemata + * @param AvroNamedSchema $schema schema to add to this existing schemata + * @throws AvroSchemaParseException */ - public function cloneWithNewSchema($schema) + public function cloneWithNewSchema(AvroNamedSchema $schema): AvroNamedSchemata { $name = $schema->fullname(); if (AvroSchema::isValidType($name)) { @@ -92,11 +85,10 @@ public function cloneWithNewSchema($schema) } /** - * @param string $fullname - * @returns boolean true if there exists a schema with the given name + * @returns bool true if there exists a schema with the given name * and false otherwise. */ - public function hasName($fullname) + public function hasName(string $fullname): bool { return array_key_exists($fullname, $this->schemata); } diff --git a/lang/php/lib/Schema/AvroPrimitiveSchema.php b/lang/php/lib/Schema/AvroPrimitiveSchema.php index 1c331a30373..6a483659ee9 100644 --- a/lang/php/lib/Schema/AvroPrimitiveSchema.php +++ b/lang/php/lib/Schema/AvroPrimitiveSchema.php @@ -31,7 +31,7 @@ class AvroPrimitiveSchema extends AvroSchema * @throws AvroSchemaParseException if the given $type is not a * primitive schema type name */ - public function __construct($type) + public function __construct(string $type) { if (!self::isPrimitiveType($type)) { throw new AvroSchemaParseException(sprintf('%s is not a valid primitive type.', $type)); @@ -111,15 +111,13 @@ public static function localTimestampMicros(): self return $self; } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { $avro = parent::toAvro(); - // FIXME: Is this if really necessary? When *wouldn't* this be the case? - if (1 == count($avro)) { + // This check has been added (I guess) to avoid printing something like this for primitive types: + // {"name": "something", "type": {"type": "string"}} + if (1 === count($avro)) { return $this->type; } diff --git a/lang/php/lib/Schema/AvroRecordSchema.php b/lang/php/lib/Schema/AvroRecordSchema.php index 959ce5904ee..570f78f5a43 100644 --- a/lang/php/lib/Schema/AvroRecordSchema.php +++ b/lang/php/lib/Schema/AvroRecordSchema.php @@ -26,32 +26,23 @@ class AvroRecordSchema extends AvroNamedSchema { /** - * @var AvroNamedSchema[] array of AvroNamedSchema field definitions of + * @var array array of AvroNamedSchema field definitions of * this AvroRecordSchema */ - private $fields; + private array $fields; /** - * @var array map of field names to field objects. + * @var null|array map of field names to field objects. * @internal Not called directly. Memoization of AvroRecordSchema->fieldsHash() */ - private $fieldsHash; + private ?array $fieldsHash = null; - /** - * @param AvroName $name - * @param string $namespace - * @param string $doc - * @param array $fields - * @param AvroNamedSchemata &$schemata - * @param string $schema_type schema type name - * @throws AvroSchemaParseException - */ public function __construct( - $name, - $doc, - $fields, - &$schemata = null, - $schema_type = AvroSchema::RECORD_SCHEMA, - $aliases = null + AvroName $name, + ?string $doc, + ?array $fields, + ?AvroNamedSchemata &$schemata = null, + string $schema_type = AvroSchema::RECORD_SCHEMA, + ?array $aliases = null ) { if (is_null($fields)) { throw new AvroSchemaParseException( @@ -59,7 +50,7 @@ public function __construct( ); } - if (AvroSchema::REQUEST_SCHEMA == $schema_type) { + if (AvroSchema::REQUEST_SCHEMA === $schema_type) { parent::__construct($schema_type, $name); } else { parent::__construct($schema_type, $name, $doc, $schemata, $aliases); @@ -70,22 +61,22 @@ public function __construct( } /** - * @param mixed $field_data - * @param string $default_namespace namespace of enclosing schema - * @param AvroNamedSchemata &$schemata - * @returns AvroField[] + * @param null|string $default_namespace namespace of enclosing schema * @throws AvroSchemaParseException */ - public static function parseFields($field_data, $default_namespace, &$schemata) - { - $fields = array(); - $field_names = array(); + public static function parseFields( + array $field_data, + ?string $default_namespace, + ?AvroNamedSchemata $schemata = null + ): array { + $fields = []; + $field_names = []; $alias_names = []; - foreach ($field_data as $index => $field) { + foreach ($field_data as $field) { $name = $field[AvroField::FIELD_NAME_ATTR] ?? null; $type = $field[AvroSchema::TYPE_ATTR] ?? null; $order = $field[AvroField::ORDER_ATTR] ?? null; - $aliases = $field[AvroField::ALIASES_ATTR] ?? null; + $aliases = $field[AvroSchema::ALIASES_ATTR] ?? null; $default = null; $has_default = false; @@ -109,22 +100,20 @@ public static function parseFields($field_data, $default_namespace, &$schemata) ) ) { $is_schema_from_schemata = true; + } else if (is_string($type) && self::isPrimitiveType($type)) { + $field_schema = self::subparse($field, $default_namespace, $schemata); } else { - if (self::isPrimitiveType($type)) { - $field_schema = self::subparse($field, $default_namespace, $schemata); - } else { - $field_schema = self::subparse($type, $default_namespace, $schemata); - } + $field_schema = self::subparse($type, $default_namespace, $schemata); } $new_field = new AvroField( - $name, - $field_schema, - $is_schema_from_schemata, - $has_default, - $default, - $order, - $aliases + name: $name, + schema: $field_schema, + isTypeFromSchemata: $is_schema_from_schemata, + hasDefault: $has_default, + default: $default, + order: $order, + aliases: $aliases ); $field_names[] = $name; if ($new_field->hasAliases() && array_intersect($alias_names, $new_field->getAliases())) { @@ -138,14 +127,11 @@ public static function parseFields($field_data, $default_namespace, &$schemata) return $fields; } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { $avro = parent::toAvro(); - $fields_avro = array(); + $fields_avro = []; foreach ($this->fields as $field) { $fields_avro[] = $field->toAvro(); } @@ -162,19 +148,19 @@ public function toAvro() /** * @returns array the schema definitions of the fields of this AvroRecordSchema */ - public function fields() + public function fields(): array { return $this->fields; } /** - * @returns array a hash table of the fields of this AvroRecordSchema fields + * @return array a hash table of the fields of this AvroRecordSchema fields * keyed by each field's name */ - public function fieldsHash() + public function fieldsHash(): array { if (is_null($this->fieldsHash)) { - $hash = array(); + $hash = []; foreach ($this->fields as $field) { $hash[$field->name()] = $field; } @@ -183,7 +169,7 @@ public function fieldsHash() return $this->fieldsHash; } - public function fieldsByAlias() + public function fieldsByAlias(): array { $hash = []; foreach ($this->fields as $field) { diff --git a/lang/php/lib/Schema/AvroSchema.php b/lang/php/lib/Schema/AvroSchema.php index 5a33ce1c9ec..4e80601c0ce 100644 --- a/lang/php/lib/Schema/AvroSchema.php +++ b/lang/php/lib/Schema/AvroSchema.php @@ -20,6 +20,7 @@ namespace Apache\Avro\Schema; +use Apache\Avro\AvroException; use Apache\Avro\AvroUtil; use Apache\Avro\Datum\Type\AvroDuration; @@ -52,7 +53,7 @@ /** * @package Avro */ -class AvroSchema +class AvroSchema implements \Stringable { /** * @var int lower bound of integer values: -(1 << 31) @@ -70,7 +71,7 @@ class AvroSchema public const INT_RANGE = 4294967296; /** - * @var int lower bound of long values: -(1 << 63) + * @var float lower bound of long values: -(1 << 63) */ public const LONG_MIN_VALUE = -9223372036854775808; @@ -268,7 +269,7 @@ class AvroSchema /** * @var array list of primitive schema type names */ - private static $primitiveTypes = array( + private static $primitiveTypes = [ self::NULL_TYPE, self::BOOLEAN_TYPE, self::STRING_TYPE, @@ -277,21 +278,21 @@ class AvroSchema self::LONG_TYPE, self::FLOAT_TYPE, self::DOUBLE_TYPE - ); + ]; /** * @var array list of named schema type names */ - private static $namedTypes = array( + private static $namedTypes = [ self::FIXED_SCHEMA, self::ENUM_SCHEMA, self::RECORD_SCHEMA, self::ERROR_SCHEMA - ); + ]; /** * @var array list of names of reserved attributes */ - private static $reservedAttrs = array( + private static $reservedAttrs = [ self::TYPE_ATTR, self::NAME_ATTR, self::NAMESPACE_ATTR, @@ -301,45 +302,45 @@ class AvroSchema self::SYMBOLS_ATTR, self::VALUES_ATTR, self::LOGICAL_TYPE_ATTR, - ); - /** - * @var string|AvroNamedSchema - */ - public $type; + ]; - /** @var null|AvroLogicalType */ - protected $logicalType = null; + protected ?AvroLogicalType $logicalType = null; /** - * @param string $type a schema type name + * @param string|AvroSchema $type a schema type name * @internal Should only be called from within the constructor of * a class which extends AvroSchema */ - public function __construct($type) - { - $this->type = $type; + public function __construct( + public readonly string|AvroSchema $type + ) { } /** - * @param string $json JSON-encoded schema * @uses self::realParse() - * @returns AvroSchema */ - public static function parse($json) + public static function parse(string $json): AvroSchema { $schemata = new AvroNamedSchemata(); - return self::realParse(json_decode($json, true), null, $schemata); + return self::realParse( + avro: json_decode($json, true, JSON_THROW_ON_ERROR), + schemata: $schemata + ); } /** - * @param mixed $avro JSON-decoded schema - * @param string $default_namespace namespace of enclosing schema - * @param AvroNamedSchemata &$schemata reference to named schemas - * @returns AvroSchema + * @param null|array|string $avro JSON-decoded schema + * @param string|null $default_namespace namespace of enclosing schema + * @param AvroNamedSchemata|null $schemata reference to named schemas + * @return AvroSchema * @throws AvroSchemaParseException + * @throws AvroException */ - public static function realParse($avro, $default_namespace = null, &$schemata = null) - { + public static function realParse( + null|array|string $avro, + ?string $default_namespace = null, + ?AvroNamedSchemata &$schemata = null + ): AvroSchema { if (is_null($schemata)) { $schemata = new AvroNamedSchemata(); } @@ -379,81 +380,80 @@ public static function realParse($avro, $default_namespace = null, &$schemata = $new_name = new AvroName($name, $namespace, $default_namespace); $doc = $avro[self::DOC_ATTR] ?? null; $aliases = $avro[self::ALIASES_ATTR] ?? null; + AvroNamedSchema::hasValidAliases($aliases); switch ($type) { case self::FIXED_SCHEMA: - $size = $avro[self::SIZE_ATTR] ?? null; + $size = $avro[self::SIZE_ATTR] ?? throw new AvroSchemaParseException( + "Size is required for fixed schema" + ); + $size = (int) $size; + if (array_key_exists(self::LOGICAL_TYPE_ATTR, $avro)) { switch ($avro[self::LOGICAL_TYPE_ATTR]) { case self::DURATION_LOGICAL_TYPE: return AvroFixedSchema::duration( - $new_name, - $doc, - $schemata, - $aliases + name: $new_name, + schemata: $schemata, + aliases: $aliases ); case self::DECIMAL_LOGICAL_TYPE: [$precision, $scale] = self::extractPrecisionAndScaleForDecimal($avro); return AvroFixedSchema::decimal( - $new_name, - $doc, - $size, - $precision, - $scale, - $schemata, - $aliases + name: $new_name, + size: $size, + precision: $precision, + scale: $scale, + schemata: $schemata, + aliases: $aliases ); } } return new AvroFixedSchema( - $new_name, - $doc, - $size, - $schemata, - $aliases + name: $new_name, + size: $size, + schemata: $schemata, + aliases: $aliases ); case self::ENUM_SCHEMA: $symbols = $avro[self::SYMBOLS_ATTR] ?? null; return new AvroEnumSchema( - $new_name, - $doc, - $symbols, - $schemata, - $aliases + name: $new_name, + doc: $doc, + symbols: $symbols, + schemata: $schemata, + aliases: $aliases ); case self::RECORD_SCHEMA: case self::ERROR_SCHEMA: $fields = $avro[self::FIELDS_ATTR] ?? null; return new AvroRecordSchema( - $new_name, - $doc, - $fields, - $schemata, - $type, - $aliases + name: $new_name, + doc: $doc, + fields: $fields, + schemata: $schemata, + schema_type: $type, + aliases: $aliases ); default: throw new AvroSchemaParseException(sprintf('Unknown named type: %s', $type)); } } elseif (self::isValidType($type)) { - switch ($type) { - case self::ARRAY_SCHEMA: - return new AvroArraySchema( - $avro[self::ITEMS_ATTR], - $default_namespace, - $schemata - ); - case self::MAP_SCHEMA: - return new AvroMapSchema( - $avro[self::VALUES_ATTR], - $default_namespace, - $schemata - ); - default: - throw new AvroSchemaParseException( - sprintf('Unknown valid type: %s', $type) - ); - } + return match ($type) { + self::ARRAY_SCHEMA => new AvroArraySchema( + items: $avro[self::ITEMS_ATTR], + defaultNamespace: $default_namespace, + schemata: $schemata + ), + self::MAP_SCHEMA => new AvroMapSchema( + values: $avro[self::VALUES_ATTR], + defaultNamespace: $default_namespace, + schemata: $schemata + ), + default => throw new AvroSchemaParseException( + sprintf('Unknown valid type: %s', $type) + ), + }; } elseif ( !array_key_exists(self::TYPE_ATTR, $avro) && AvroUtil::isList($avro) @@ -486,39 +486,39 @@ public static function isValidType($type) { return (self::isPrimitiveType($type) || self::isNamedType($type) - || in_array($type, array( + || in_array($type, [ self::ARRAY_SCHEMA, self::MAP_SCHEMA, self::UNION_SCHEMA, self::REQUEST_SCHEMA, self::ERROR_UNION_SCHEMA - ))); + ])); } /** - * @param string $type a schema type name + * @param null|string $type a schema type name * @returns boolean true if the given type name is a primitive schema type * name and false otherwise. */ - public static function isPrimitiveType($type) + public static function isPrimitiveType(?string $type): bool { - return in_array($type, self::$primitiveTypes); + return in_array($type, self::$primitiveTypes, true); } /** - * @param string $type a schema type name - * @returns boolean true if the given type name is a named schema type name + * @param null|string $type a schema type name + * @returns bool true if the given type name is a named schema type name * and false otherwise. */ - public static function isNamedType($type) + public static function isNamedType(?string $type): bool { - return in_array($type, self::$namedTypes); + return in_array($type, self::$namedTypes, true); } - public static function hasValidAliases($aliases) + public static function hasValidAliases($aliases): void { if ($aliases === null) { - return false; + return; } if (!is_array($aliases)) { throw new AvroSchemaParseException( @@ -561,7 +561,7 @@ public static function isValidDatum(AvroSchema $expected_schema, $datum): bool case self::DOUBLE_TYPE: return (is_float($datum) || is_int($datum)); case self::ARRAY_SCHEMA: - if (is_array($datum)) { + if (is_array($datum) && $expected_schema instanceof AvroArraySchema) { foreach ($datum as $d) { if (!self::isValidDatum($expected_schema->items(), $d)) { return false; @@ -571,7 +571,7 @@ public static function isValidDatum(AvroSchema $expected_schema, $datum): bool } return false; case self::MAP_SCHEMA: - if (is_array($datum)) { + if (is_array($datum) && $expected_schema instanceof AvroMapSchema) { foreach ($datum as $k => $v) { if ( !is_string($k) @@ -584,6 +584,10 @@ public static function isValidDatum(AvroSchema $expected_schema, $datum): bool } return false; case self::UNION_SCHEMA: + if (!$expected_schema instanceof AvroUnionSchema) { + return false; + } + foreach ($expected_schema->schemas() as $schema) { if (self::isValidDatum($schema, $datum)) { return true; @@ -591,8 +595,15 @@ public static function isValidDatum(AvroSchema $expected_schema, $datum): bool } return false; case self::ENUM_SCHEMA: - return in_array($datum, $expected_schema->symbols()); + if (!$expected_schema instanceof AvroEnumSchema) { + return false; + } + return in_array($datum, $expected_schema->symbols(), true); case self::FIXED_SCHEMA: + if (!$expected_schema instanceof AvroFixedSchema) { + return false; + } + if ( $expected_schema->logicalType() instanceof AvroLogicalType ) { @@ -618,6 +629,10 @@ public static function isValidDatum(AvroSchema $expected_schema, $datum): bool case self::RECORD_SCHEMA: case self::ERROR_SCHEMA: case self::REQUEST_SCHEMA: + if (!($expected_schema instanceof AvroRecordSchema)) { + return false; + } + if (is_array($datum)) { foreach ($expected_schema->fields() as $field) { if (!self::isValidDatum($field->type(), $datum[$field->name()] ?? null)) { @@ -634,19 +649,17 @@ public static function isValidDatum(AvroSchema $expected_schema, $datum): bool /** * @param mixed $avro - * @param string $default_namespace namespace of enclosing schema - * @param AvroNamedSchemata &$schemata * @returns AvroSchema * @throws AvroSchemaParseException * @uses AvroSchema::realParse() */ - protected static function subparse($avro, $default_namespace, &$schemata = null) + protected static function subparse($avro, ?string $default_namespace, ?AvroNamedSchemata &$schemata = null): AvroSchema { try { return self::realParse($avro, $default_namespace, $schemata); } catch (AvroSchemaParseException $e) { throw $e; - } catch (\Exception $e) { + } catch (\Throwable) { throw new AvroSchemaParseException( sprintf( 'Sub-schema is not a valid Avro schema. Bad schema: %s', @@ -672,12 +685,12 @@ public function logicalType(): ?AvroLogicalType /** * @returns string the JSON-encoded representation of this Avro schema. */ - public function __toString() + public function __toString(): string { - return (string) json_encode($this->toAvro()); + return json_encode($this->toAvro(), JSON_THROW_ON_ERROR); } - public function toAvro() + public function toAvro(): string|array { $avro = [self::TYPE_ATTR => $this->type]; diff --git a/lang/php/lib/Schema/AvroUnionSchema.php b/lang/php/lib/Schema/AvroUnionSchema.php index 796372a9d19..636cee3a8d1 100644 --- a/lang/php/lib/Schema/AvroUnionSchema.php +++ b/lang/php/lib/Schema/AvroUnionSchema.php @@ -31,23 +31,24 @@ class AvroUnionSchema extends AvroSchema * @var int[] list of indices of named schemas which * are defined in $schemata */ - public $schemaFromSchemataIndices; + public array $schemaFromSchemataIndices; /** * @var AvroSchema[] list of schemas of this union */ - private $schemas; + private array $schemas; /** * @param AvroSchema[] $schemas list of schemas in the union - * @param string $defaultNamespace namespace of enclosing schema - * @param AvroNamedSchemata &$schemata + * @param null|string $defaultNamespace namespace of enclosing schema + * @param null|AvroNamedSchemata &$schemata + * @throws AvroSchemaParseException */ - public function __construct($schemas, $defaultNamespace, &$schemata = null) + public function __construct(array $schemas, ?string $defaultNamespace, ?AvroNamedSchemata &$schemata = null) { parent::__construct(AvroSchema::UNION_SCHEMA); - $this->schemaFromSchemataIndices = array(); - $schema_types = array(); + $this->schemaFromSchemataIndices = []; + $schema_types = []; foreach ($schemas as $index => $schema) { $is_schema_from_schemata = false; $new_schema = null; @@ -86,17 +87,17 @@ public function __construct($schemas, $defaultNamespace, &$schemata = null) /** * @returns AvroSchema[] */ - public function schemas() + public function schemas(): array { return $this->schemas; } /** - * @returns AvroSchema the particular schema from the union for + * @return AvroSchema the particular schema from the union for * the given (zero-based) index. * @throws AvroSchemaParseException if the index is invalid for this schema. */ - public function schemaByIndex($index) + public function schemaByIndex($index): AvroSchema { if (count($this->schemas) > $index) { return $this->schemas[$index]; @@ -105,15 +106,12 @@ public function schemaByIndex($index) throw new AvroSchemaParseException('Invalid union schema index'); } - /** - * @returns mixed - */ - public function toAvro() + public function toAvro(): string|array { - $avro = array(); + $avro = []; foreach ($this->schemas as $index => $schema) { - $avro[] = in_array($index, $this->schemaFromSchemataIndices) + $avro[] = in_array($index, $this->schemaFromSchemataIndices) && $schema instanceof AvroNamedSchema ? $schema->qualifiedName() : $schema->toAvro(); } diff --git a/lang/php/phpstan.neon b/lang/php/phpstan.neon new file mode 100644 index 00000000000..1eae92a3e47 --- /dev/null +++ b/lang/php/phpstan.neon @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +parameters: + level: 3 + paths: + - lib + - test + bootstrapFiles: + - test/test_helper.php diff --git a/lang/php/phpunit.xml b/lang/php/phpunit.xml index 10584bc4aad..f4f5ead0114 100644 --- a/lang/php/phpunit.xml +++ b/lang/php/phpunit.xml @@ -15,30 +15,33 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - - - - - test - test/InterOpTest.php - - - - - - lib - - + + + + + + + test + test/InterOpTest.php + + + + + lib + + diff --git a/lang/php/test/DataFileTest.php b/lang/php/test/DataFileTest.php index d962866a828..dd8d77b3370 100644 --- a/lang/php/test/DataFileTest.php +++ b/lang/php/test/DataFileTest.php @@ -1,4 +1,5 @@ remove_data_files(); + $this->dataFiles = []; + } - public function test_write_read_nothing_round_trip() + protected function tearDown(): void + { + $this->remove_data_files(); + } + + public function test_write_read_nothing_round_trip(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } @@ -49,28 +65,17 @@ public function test_write_read_nothing_round_trip() } } - protected function add_data_file($data_file) - { - if (is_null($this->data_files)) { - $this->data_files = array(); - } - $data_file = "$data_file." . self::current_timestamp(); - $full = join(DIRECTORY_SEPARATOR, array(TEST_TEMP_DIR, $data_file)); - $this->data_files [] = $full; - return $full; - } - - public static function current_timestamp() + public static function current_timestamp(): string { - return date_format(new DateTime, 'Y-m-d H:i:s'); + return (new \DateTime())->format('Y-m-d H:i:s'); } - public function test_write_read_null_round_trip() + public function test_write_read_null_round_trip(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } @@ -89,12 +94,12 @@ public function test_write_read_null_round_trip() } } - public function test_write_read_string_round_trip() + public function test_write_read_string_round_trip(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } @@ -113,12 +118,12 @@ public function test_write_read_string_round_trip() } } - public function test_write_read_round_trip() + public function test_write_read_round_trip(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } @@ -138,12 +143,12 @@ public function test_write_read_round_trip() } } - public function test_write_read_true_round_trip() + public function test_write_read_true_round_trip(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } @@ -162,12 +167,12 @@ public function test_write_read_true_round_trip() } } - public function test_write_read_false_round_trip() + public function test_write_read_false_round_trip(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } @@ -186,18 +191,18 @@ public function test_write_read_false_round_trip() } } - public function test_write_read_int_array_round_trip() + public function test_write_read_int_array_round_trip(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } $data_file = $this->add_data_file(sprintf('data-wr-int-ary-%s.avr', $codec)); $writers_schema = '"int"'; - $data = array(10, 20, 30, 40, 50, 60, 70, 567, 89012345); + $data = [10, 20, 30, 40, 50, 60, 70, 567, 89012345]; $dw = AvroDataIO::openFile($data_file, 'w', $writers_schema, $codec); foreach ($data as $datum) { $dw->append($datum); @@ -207,48 +212,55 @@ public function test_write_read_int_array_round_trip() $dr = AvroDataIO::openFile($data_file); $read_data = $dr->data(); $dr->close(); - $this->assertEquals($data, $read_data, - sprintf("in: %s\nout: %s", - json_encode($data), json_encode($read_data))); + $this->assertEquals( + $data, + $read_data, + sprintf( + "in: %s\nout: %s", + json_encode($data), + json_encode($read_data) + ) + ); } } - public function test_differing_schemas_with_primitives() + public function test_differing_schemas_with_primitives(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } $data_file = $this->add_data_file(sprintf('data-prim-%s.avr', $codec)); $writer_schema = << 'john', 'age' => 25, 'verified' => true), - array('username' => 'ryan', 'age' => 23, 'verified' => false) - ); + { "type": "record", + "name": "User", + "fields" : [ + {"name": "username", "type": "string"}, + {"name": "age", "type": "int"}, + {"name": "verified", "type": "boolean", "default": "false"} + ] + } + JSON; + $data = [ + ['username' => 'john', 'age' => 25, 'verified' => true], + ['username' => 'ryan', 'age' => 23, 'verified' => false], + ]; $dw = AvroDataIO::openFile($data_file, 'w', $writer_schema, $codec); foreach ($data as $datum) { $dw->append($datum); } $dw->close(); $reader_schema = <<data() as $index => $record) { $this->assertEquals($data[$index]['username'], $record['username']); @@ -256,36 +268,75 @@ public function test_differing_schemas_with_primitives() } } - public function test_differing_schemas_with_complex_objects() + public function test_differing_schemas_with_complex_objects(): void { foreach (AvroDataIO::validCodecs() as $codec) { if ( - ($codec === AvroDataIO::SNAPPY_CODEC && !extension_loaded('snappy')) - || ($codec === AvroDataIO::ZSTANDARD_CODEC && !extension_loaded('zstd')) + (AvroDataIO::SNAPPY_CODEC === $codec && !extension_loaded('snappy')) + || (AvroDataIO::ZSTANDARD_CODEC === $codec && !extension_loaded('zstd')) ) { continue; } $data_file = $this->add_data_file(sprintf('data-complex-%s.avr', $codec)); $writers_schema = << [1, 2, 3], "something_map" => ["a" => 1, "b" => 2], "something_record" => ["inner" => 2], - "something_error" => ["code" => 403] + "something_error" => ["code" => 403], ], [ "username" => "ryan", @@ -304,8 +355,8 @@ public function test_differing_schemas_with_complex_objects() "something_array" => [1, 2, 3], "something_map" => ["a" => 2, "b" => 6], "something_record" => ["inner" => 1], - "something_error" => ["code" => 401] - ] + "something_error" => ["code" => 401], + ], ]; $dw = AvroDataIO::openFile($data_file, 'w', $writers_schema, $codec); foreach ($data as $datum) { @@ -313,15 +364,15 @@ public function test_differing_schemas_with_complex_objects() } $dw->close(); - foreach (array( - 'fixed', - 'enum', - 'record', - 'error', - 'array', - 'map', - 'union' - ) as $s) { + foreach ([ + 'fixed', + 'enum', + 'record', + 'error', + 'array', + 'map', + 'union', + ] as $s) { $readers_schema = json_decode($writers_schema, true); $dr = AvroDataIO::openFile($data_file, 'r', json_encode($readers_schema)); foreach ($dr->data() as $idx => $obj) { @@ -335,32 +386,28 @@ public function test_differing_schemas_with_complex_objects() } } - protected function setUp(): void + protected function add_data_file(string $data_file): string { - if (!file_exists(TEST_TEMP_DIR)) { - mkdir(TEST_TEMP_DIR); - } - $this->remove_data_files(); + $data_file = "$data_file.".self::current_timestamp(); + $full = implode(DIRECTORY_SEPARATOR, [TEST_TEMP_DIR, $data_file]); + $this->dataFiles[] = $full; + + return $full; } - protected function remove_data_files() + protected function remove_data_files(): void { - if (self::REMOVE_DATA_FILES && $this->data_files) { - foreach ($this->data_files as $data_file) { + if (self::REMOVE_DATA_FILES && $this->dataFiles) { + foreach ($this->dataFiles as $data_file) { self::remove_data_file($data_file); } } } - protected static function remove_data_file($data_file) + protected static function remove_data_file($data_file): void { if (file_exists($data_file)) { unlink($data_file); } } - - protected function tearDown(): void - { - $this->remove_data_files(); - } } diff --git a/lang/php/test/DatumIOTest.php b/lang/php/test/DatumIOTest.php index a29cf06f064..717ee78a247 100644 --- a/lang/php/test/DatumIOTest.php +++ b/lang/php/test/DatumIOTest.php @@ -1,4 +1,5 @@ assertIsValidDatumForSchema($schema_json, $datum, $binary); } @@ -61,11 +55,11 @@ public static function data_provider(): array ['"int"', 2147483647, "\xFE\xFF\xFF\xFF\x0F"], ['"long"', (int) -9223372036854775808, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"], - ['"long"', -(1<<62), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"], - ['"long"', -(1<<61), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F"], + ['"long"', -(1 << 62), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"], + ['"long"', -(1 << 61), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F"], ['"long"', -4294967295, "\xFD\xFF\xFF\xFF\x1F"], - ['"long"', -1<<24, "\xFF\xFF\xFF\x0F"], - ['"long"', -1<<16, "\xFF\xFF\x07"], + ['"long"', -1 << 24, "\xFF\xFF\xFF\x0F"], + ['"long"', -1 << 16, "\xFF\xFF\x07"], ['"long"', -255, "\xFD\x03"], ['"long"', -128, "\xFF\x01"], ['"long"', -127, "\xFD\x01"], @@ -81,11 +75,11 @@ public static function data_provider(): array ['"long"', 127, "\xFE\x01"], ['"long"', 128, "\x80\x02"], ['"long"', 255, "\xFE\x03"], - ['"long"', 1<<16, "\x80\x80\x08"], - ['"long"', 1<<24, "\x80\x80\x80\x10"], + ['"long"', 1 << 16, "\x80\x80\x08"], + ['"long"', 1 << 24, "\x80\x80\x80\x10"], ['"long"', 4294967295, "\xFE\xFF\xFF\xFF\x1F"], - ['"long"', 1<<61, "\x80\x80\x80\x80\x80\x80\x80\x80\x40"], - ['"long"', 1<<62, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01"], + ['"long"', 1 << 61, "\x80\x80\x80\x80\x80\x80\x80\x80\x40"], + ['"long"', 1 << 62, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01"], ['"long"', 9223372036854775807, "\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01"], ['"float"', (float) -10.0, "\000\000 \301"], @@ -94,11 +88,11 @@ public static function data_provider(): array ['"float"', (float) 2.0, "\000\000\000@"], ['"float"', (float) 9.0, "\000\000\020A"], - ['"double"', (double) -10.0, "\000\000\000\000\000\000$\300"], - ['"double"', (double) -1.0, "\000\000\000\000\000\000\360\277"], - ['"double"', (double) 0.0, "\000\000\000\000\000\000\000\000"], - ['"double"', (double) 2.0, "\000\000\000\000\000\000\000@"], - ['"double"', (double) 9.0, "\000\000\000\000\000\000\"@"], + ['"double"', (float) -10.0, "\000\000\000\000\000\000$\300"], + ['"double"', (float) -1.0, "\000\000\000\000\000\000\360\277"], + ['"double"', (float) 0.0, "\000\000\000\000\000\000\000\000"], + ['"double"', (float) 2.0, "\000\000\000\000\000\000\000@"], + ['"double"', (float) 9.0, "\000\000\000\000\000\000\"@"], ['"string"', 'foo', "\x06foo"], ['"bytes"', "\x01\x02\x03", "\x06\x01\x02\x03"], @@ -106,28 +100,28 @@ public static function data_provider(): array [ '{"type":"array","items":"int"}', [1, 2, 3], - "\x06\x02\x04\x06\x00" + "\x06\x02\x04\x06\x00", ], [ '{"type":"map","values":"int"}', ['foo' => 1, 'bar' => 2, 'baz' => 3], - "\x06\x06foo\x02\x06bar\x04\x06baz\x06\x00" + "\x06\x06foo\x02\x06bar\x04\x06baz\x06\x00", ], ['["null", "int"]', 1, "\x02\x02"], [ '{"name":"fix","type":"fixed","size":3}', "\xAA\xBB\xCC", - "\xAA\xBB\xCC" + "\xAA\xBB\xCC", ], [ '{"name":"enm","type":"enum","symbols":["A","B","C"]}', 'B', - "\x02" + "\x02", ], [ '{"name":"rec","type":"record","fields":[{"name":"a","type":"int"},{"name":"b","type":"boolean"}]}', ['a' => 1, 'b' => false], - "\x02\x00" + "\x02\x00", ], ]; } @@ -216,48 +210,46 @@ public static function validBytesDecimalLogicalType(): array ]; } - /** - * @dataProvider validBytesDecimalLogicalType - */ - public function testValidBytesDecimalLogicalType(string $datum, int $precision, int $scale, string $expected): void + #[DataProvider('validBytesDecimalLogicalType')] + public function test_valid_bytes_decimal_logical_type(string $datum, int $precision, int $scale, string $expected): void { $bytesSchemaJson = <<assertIsValidDatumForSchema($bytesSchemaJson, $datum, $expected); $fixedSchemaJson = <<assertIsValidDatumForSchema($fixedSchemaJson, $datum, $expected); } - public function testInvalidBytesLogicalTypeOutOfRange(): void + public function test_invalid_bytes_logical_type_out_of_range(): void { $schemaJson = <<assertIsValidDatumForSchema( $bytesSchemaJson, @@ -311,10 +301,8 @@ public static function durationLogicalTypeOutOfBounds(): array ]; } - /** - * @dataProvider durationLogicalTypeOutOfBounds - */ - public function testDurationLogicalTypeOutOfBounds(int $months, int $days, int $milliseconds): void + #[DataProvider('durationLogicalTypeOutOfBounds')] + public function test_duration_logical_type_out_of_bounds(int $months, int $days, int $milliseconds): void { $this->expectException(AvroException::class); new AvroDuration($months, $days, $milliseconds); @@ -365,7 +353,7 @@ public static function default_provider(): array [ '{"type":"array","items":"int"}', '[5,4,3,2]', - [5, 4, 3, 2] + [5, 4, 3, 2], ], [ '{"type":"map","values":"int"}', @@ -407,20 +395,20 @@ public static function default_provider(): array ]; } - /** - * @dataProvider default_provider - */ + #[DataProvider('default_provider')] public function test_field_default_value( - $field_schema_json, - $default_json, - $default_value + string $field_schema_json, + string $default_json, + mixed $default_value ): void { $writers_schema_json = '{"name":"foo","type":"record","fields":[]}'; $writers_schema = AvroSchema::parse($writers_schema_json); $readers_schema_json = sprintf( '{"name":"foo","type":"record","fields":[{"name":"f","type":%s,"default":%s}]}', - $field_schema_json, $default_json); + $field_schema_json, + $default_json + ); $readers_schema = AvroSchema::parse($readers_schema_json); $reader = new AvroIODatumReader($writers_schema, $readers_schema); @@ -428,16 +416,16 @@ public function test_field_default_value( if (array_key_exists('f', $record)) { $this->assertEquals($default_value, $record['f']); } else { - $this->assertTrue(false, sprintf('expected field record[f]: %s', - print_r($record, true))); + $this->assertTrue(false, sprintf( + 'expected field record[f]: %s', + print_r($record, true) + )); } } /** - * @param string $schemaJson * @param string $datum * @param string $expected - * @return void * @throws \Apache\Avro\IO\AvroIOException */ private function assertIsValidDatumForSchema(string $schemaJson, $datum, $expected): void @@ -449,10 +437,15 @@ private function assertIsValidDatumForSchema(string $schemaJson, $datum, $expect $writer->write($datum, $encoder); $output = (string) $written; - $this->assertEquals($expected, $output, - sprintf("expected: %s\n actual: %s", + $this->assertEquals( + $expected, + $output, + sprintf( + "expected: %s\n actual: %s", AvroDebug::asciiString($expected, 'hex'), - AvroDebug::asciiString($output, 'hex'))); + AvroDebug::asciiString($output, 'hex') + ) + ); $read = new AvroStringIO((string) $expected); $decoder = new AvroIOBinaryDecoder($read); diff --git a/lang/php/test/FloatIntEncodingTest.php b/lang/php/test/FloatIntEncodingTest.php index 07492cfadae..24afc849c62 100644 --- a/lang/php/test/FloatIntEncodingTest.php +++ b/lang/php/test/FloatIntEncodingTest.php @@ -1,4 +1,5 @@ assertIsFloat(self::$FLOAT_NAN, 'float NaN is a float'); - $this->assertTrue(is_nan(self::$FLOAT_NAN), 'float NaN is NaN'); - $this->assertFalse(is_infinite(self::$FLOAT_NAN), 'float NaN is not infinite'); - - $this->assertIsFloat(self::$FLOAT_POS_INF, 'float pos infinity is a float'); - $this->assertTrue(is_infinite(self::$FLOAT_POS_INF), 'float pos infinity is infinite'); - $this->assertTrue(0 < self::$FLOAT_POS_INF, 'float pos infinity is greater than 0'); - $this->assertFalse(is_nan(self::$FLOAT_POS_INF), 'float pos infinity is not NaN'); - - $this->assertIsFloat(self::$FLOAT_NEG_INF, 'float neg infinity is a float'); - $this->assertTrue(is_infinite(self::$FLOAT_NEG_INF), 'float neg infinity is infinite'); - $this->assertTrue(0 > self::$FLOAT_NEG_INF, 'float neg infinity is less than 0'); - $this->assertFalse(is_nan(self::$FLOAT_NEG_INF), 'float neg infinity is not NaN'); - - $this->assertIsFloat(self::$DOUBLE_NAN, 'double NaN is a double'); - $this->assertTrue(is_nan(self::$DOUBLE_NAN), 'double NaN is NaN'); - $this->assertFalse(is_infinite(self::$DOUBLE_NAN), 'double NaN is not infinite'); - - $this->assertIsFloat(self::$DOUBLE_POS_INF, 'double pos infinity is a double'); - $this->assertTrue(is_infinite(self::$DOUBLE_POS_INF), 'double pos infinity is infinite'); - $this->assertTrue(0 < self::$DOUBLE_POS_INF, 'double pos infinity is greater than 0'); - $this->assertFalse(is_nan(self::$DOUBLE_POS_INF), 'double pos infinity is not NaN'); - - $this->assertIsFloat(self::$DOUBLE_NEG_INF, 'double neg infinity is a double'); - $this->assertTrue(is_infinite(self::$DOUBLE_NEG_INF), 'double neg infinity is infinite'); - $this->assertTrue(0 > self::$DOUBLE_NEG_INF, 'double neg infinity is less than 0'); - $this->assertFalse(is_nan(self::$DOUBLE_NEG_INF), 'double neg infinity is not NaN'); - - } - - function special_vals_provider() - { - self::make_special_vals(); - return array(array(self::DOUBLE_TYPE, self::$DOUBLE_POS_INF, self::$LONG_BITS_POS_INF), - array(self::DOUBLE_TYPE, self::$DOUBLE_NEG_INF, self::$LONG_BITS_NEG_INF), - array(self::FLOAT_TYPE, self::$FLOAT_POS_INF, self::$INT_BITS_POS_INF), - array(self::FLOAT_TYPE, self::$FLOAT_NEG_INF, self::$INT_BITS_NEG_INF)); - } - - /** - * @dataProvider special_vals_provider - */ - function test_encoding_special_values($type, $val, $bits) - { - $this->assert_encode_values($type, $val, $bits); - } - - function nan_vals_provider() - { - self::make_special_vals(); - return array(array(self::DOUBLE_TYPE, self::$DOUBLE_NAN, self::$LONG_BITS_NAN), - array(self::FLOAT_TYPE, self::$FLOAT_NAN, self::$INT_BITS_NAN)); - } - - /** - * @dataProvider nan_vals_provider - */ - function test_encoding_nan_values($type, $val, $bits) - { - $this->assert_encode_nan_values($type, $val, $bits); - } - - function normal_vals_provider() - { - $ruby_to_generate_vals =<<<_RUBY - def d2lb(d); [d].pack('E') end - dary = (-10..10).to_a + [-1234.2132, -211e23] - dary.each {|x| b = d2lb(x); puts %/array(self::DOUBLE_TYPE, (double) #{x}, #{b.inspect}, '#{b.unpack('h*')[0]}'),/} - def f2ib(f); [f].pack('e') end - fary = (-10..10).to_a + [-1234.5, -211.3e6] - fary.each {|x| b = f2ib(x); puts %/array(self::FLOAT_TYPE, (float) #{x}, #{b.inspect}, '#{b.unpack('h*')[0]}'),/} -_RUBY; - - return array( - array(self::DOUBLE_TYPE, (double) -10, "\000\000\000\000\000\000$\300", '000000000000420c'), - array(self::DOUBLE_TYPE, (double) -9, "\000\000\000\000\000\000\"\300", '000000000000220c'), - array(self::DOUBLE_TYPE, (double) -8, "\000\000\000\000\000\000 \300", '000000000000020c'), - array(self::DOUBLE_TYPE, (double) -7, "\000\000\000\000\000\000\034\300", '000000000000c10c'), - array(self::DOUBLE_TYPE, (double) -6, "\000\000\000\000\000\000\030\300", '000000000000810c'), - array(self::DOUBLE_TYPE, (double) -5, "\000\000\000\000\000\000\024\300", '000000000000410c'), - array(self::DOUBLE_TYPE, (double) -4, "\000\000\000\000\000\000\020\300", '000000000000010c'), - /**/ array(self::DOUBLE_TYPE, (double) -3, "\000\000\000\000\000\000\010\300", '000000000000800c'), - array(self::DOUBLE_TYPE, (double) -2, "\000\000\000\000\000\000\000\300", '000000000000000c'), - array(self::DOUBLE_TYPE, (double) -1, "\000\000\000\000\000\000\360\277", '0000000000000ffb'), - array(self::DOUBLE_TYPE, (double) 0, "\000\000\000\000\000\000\000\000", '0000000000000000'), - array(self::DOUBLE_TYPE, (double) 1, "\000\000\000\000\000\000\360?", '0000000000000ff3'), - array(self::DOUBLE_TYPE, (double) 2, "\000\000\000\000\000\000\000@", '0000000000000004'), - /**/ array(self::DOUBLE_TYPE, (double) 3, "\000\000\000\000\000\000\010@", '0000000000008004'), - array(self::DOUBLE_TYPE, (double) 4, "\000\000\000\000\000\000\020@", '0000000000000104'), - array(self::DOUBLE_TYPE, (double) 5, "\000\000\000\000\000\000\024@", '0000000000004104'), - array(self::DOUBLE_TYPE, (double) 6, "\000\000\000\000\000\000\030@", '0000000000008104'), - array(self::DOUBLE_TYPE, (double) 7, "\000\000\000\000\000\000\034@", '000000000000c104'), - array(self::DOUBLE_TYPE, (double) 8, "\000\000\000\000\000\000 @", '0000000000000204'), - array(self::DOUBLE_TYPE, (double) 9, "\000\000\000\000\000\000\"@", '0000000000002204'), - array(self::DOUBLE_TYPE, (double) 10, "\000\000\000\000\000\000$@", '0000000000004204'), - /**/ array(self::DOUBLE_TYPE, (double) -1234.2132, "\007\316\031Q\332H\223\300", '70ec9115ad84390c'), - array(self::DOUBLE_TYPE, (double) -2.11e+25, "\311\260\276J\031t1\305", '9c0beba49147135c'), - - array(self::FLOAT_TYPE, (float) -10, "\000\000 \301", '0000021c'), - array(self::FLOAT_TYPE, (float) -9, "\000\000\020\301", '0000011c'), - array(self::FLOAT_TYPE, (float) -8, "\000\000\000\301", '0000001c'), - array(self::FLOAT_TYPE, (float) -7, "\000\000\340\300", '00000e0c'), - array(self::FLOAT_TYPE, (float) -6, "\000\000\300\300", '00000c0c'), - array(self::FLOAT_TYPE, (float) -5, "\000\000\240\300", '00000a0c'), - array(self::FLOAT_TYPE, (float) -4, "\000\000\200\300", '0000080c'), - array(self::FLOAT_TYPE, (float) -3, "\000\000@\300", '0000040c'), - array(self::FLOAT_TYPE, (float) -2, "\000\000\000\300", '0000000c'), - array(self::FLOAT_TYPE, (float) -1, "\000\000\200\277", '000008fb'), - array(self::FLOAT_TYPE, (float) 0, "\000\000\000\000", '00000000'), - array(self::FLOAT_TYPE, (float) 1, "\000\000\200?", '000008f3'), - array(self::FLOAT_TYPE, (float) 2, "\000\000\000@", '00000004'), - array(self::FLOAT_TYPE, (float) 3, "\000\000@@", '00000404'), - array(self::FLOAT_TYPE, (float) 4, "\000\000\200@", '00000804'), - array(self::FLOAT_TYPE, (float) 5, "\000\000\240@", '00000a04'), - array(self::FLOAT_TYPE, (float) 6, "\000\000\300@", '00000c04'), - array(self::FLOAT_TYPE, (float) 7, "\000\000\340@", '00000e04'), - array(self::FLOAT_TYPE, (float) 8, "\000\000\000A", '00000014'), - array(self::FLOAT_TYPE, (float) 9, "\000\000\020A", '00000114'), - array(self::FLOAT_TYPE, (float) 10, "\000\000 A", '00000214'), - array(self::FLOAT_TYPE, (float) -1234.5, "\000P\232\304", '0005a94c'), - array(self::FLOAT_TYPE, (float) -211300000.0, "\352\202I\315", 'ae2894dc'), - ); - } - - function float_vals_provider() - { - $ary = array(); - - foreach ($this->normal_vals_provider() as $values) - if (self::FLOAT_TYPE == $values[0]) - $ary []= array($values[0], $values[1], $values[2]); - - return $ary; - } - - function double_vals_provider() - { - $ary = array(); - - foreach ($this->normal_vals_provider() as $values) - if (self::DOUBLE_TYPE == $values[0]) - $ary []= array($values[0], $values[1], $values[2]); - - return $ary; - } - - - /** - * @dataProvider float_vals_provider - */ - function test_encoding_float_values($type, $val, $bits) - { - $this->assert_encode_values($type, $val, $bits); - } - - /** - * @dataProvider double_vals_provider - */ - function test_encoding_double_values($type, $val, $bits) - { - $this->assert_encode_values($type, $val, $bits); - } - - function assert_encode_values($type, $val, $bits) - { - if (self::FLOAT_TYPE == $type) + public const FLOAT_TYPE = 'float'; + public const DOUBLE_TYPE = 'double'; + + public static float $FLOAT_NAN; + public static float $FLOAT_POS_INF; + public static float $FLOAT_NEG_INF; + public static float $DOUBLE_NAN; + public static float $DOUBLE_POS_INF; + public static float $DOUBLE_NEG_INF; + + public static string $LONG_BITS_NAN; + public static string $LONG_BITS_POS_INF; + public static string $LONG_BITS_NEG_INF; + public static string $INT_BITS_NAN; + public static string $INT_BITS_POS_INF; + public static string $INT_BITS_NEG_INF; + + public function setUp(): void + { + self::make_special_vals(); + } + + public static function make_special_vals() + { + self::$DOUBLE_NAN = (float) NAN; + self::$DOUBLE_POS_INF = (float) INF; + self::$DOUBLE_NEG_INF = (float) -INF; + self::$FLOAT_NAN = (float) NAN; + self::$FLOAT_POS_INF = (float) INF; + self::$FLOAT_NEG_INF = (float) -INF; + + self::$LONG_BITS_NAN = strrev(pack('H*', '7ff8000000000000')); + self::$LONG_BITS_POS_INF = strrev(pack('H*', '7ff0000000000000')); + self::$LONG_BITS_NEG_INF = strrev(pack('H*', 'fff0000000000000')); + self::$INT_BITS_NAN = strrev(pack('H*', '7fc00000')); + self::$INT_BITS_POS_INF = strrev(pack('H*', '7f800000')); + self::$INT_BITS_NEG_INF = strrev(pack('H*', 'ff800000')); + } + + public function test_special_values(): void + { + $this->assertIsFloat(self::$FLOAT_NAN, 'float NaN is a float'); + $this->assertTrue(is_nan(self::$FLOAT_NAN), 'float NaN is NaN'); + $this->assertFalse(is_infinite(self::$FLOAT_NAN), 'float NaN is not infinite'); + + $this->assertIsFloat(self::$FLOAT_POS_INF, 'float pos infinity is a float'); + $this->assertTrue(is_infinite(self::$FLOAT_POS_INF), 'float pos infinity is infinite'); + $this->assertTrue(0 < self::$FLOAT_POS_INF, 'float pos infinity is greater than 0'); + $this->assertFalse(is_nan(self::$FLOAT_POS_INF), 'float pos infinity is not NaN'); + + $this->assertIsFloat(self::$FLOAT_NEG_INF, 'float neg infinity is a float'); + $this->assertTrue(is_infinite(self::$FLOAT_NEG_INF), 'float neg infinity is infinite'); + $this->assertTrue(0 > self::$FLOAT_NEG_INF, 'float neg infinity is less than 0'); + $this->assertFalse(is_nan(self::$FLOAT_NEG_INF), 'float neg infinity is not NaN'); + + $this->assertIsFloat(self::$DOUBLE_NAN, 'double NaN is a double'); + $this->assertTrue(is_nan(self::$DOUBLE_NAN), 'double NaN is NaN'); + $this->assertFalse(is_infinite(self::$DOUBLE_NAN), 'double NaN is not infinite'); + + $this->assertIsFloat(self::$DOUBLE_POS_INF, 'double pos infinity is a double'); + $this->assertTrue(is_infinite(self::$DOUBLE_POS_INF), 'double pos infinity is infinite'); + $this->assertTrue(0 < self::$DOUBLE_POS_INF, 'double pos infinity is greater than 0'); + $this->assertFalse(is_nan(self::$DOUBLE_POS_INF), 'double pos infinity is not NaN'); + + $this->assertIsFloat(self::$DOUBLE_NEG_INF, 'double neg infinity is a double'); + $this->assertTrue(is_infinite(self::$DOUBLE_NEG_INF), 'double neg infinity is infinite'); + $this->assertTrue(0 > self::$DOUBLE_NEG_INF, 'double neg infinity is less than 0'); + $this->assertFalse(is_nan(self::$DOUBLE_NEG_INF), 'double neg infinity is not NaN'); + + } + + public static function special_vals_provider(): array + { + self::make_special_vals(); + + return [ + [self::DOUBLE_TYPE, self::$DOUBLE_POS_INF, self::$LONG_BITS_POS_INF], + [self::DOUBLE_TYPE, self::$DOUBLE_NEG_INF, self::$LONG_BITS_NEG_INF], + [self::FLOAT_TYPE, self::$FLOAT_POS_INF, self::$INT_BITS_POS_INF], + [self::FLOAT_TYPE, self::$FLOAT_NEG_INF, self::$INT_BITS_NEG_INF], + ]; + } + + #[DataProvider('special_vals_provider')] + public function test_encoding_special_values(string $type, mixed $val, mixed $bits): void + { + $this->assert_encode_values($type, $val, $bits); + } + + public static function nan_vals_provider(): array + { + self::make_special_vals(); + + return [ + [self::DOUBLE_TYPE, self::$DOUBLE_NAN, self::$LONG_BITS_NAN], + [self::FLOAT_TYPE, self::$FLOAT_NAN, self::$INT_BITS_NAN], + ]; + } + + #[DataProvider('nan_vals_provider')] + public function test_encoding_nan_values(string $type, float $val, string $bits): void { - $decoder = array(AvroIOBinaryDecoder::class, 'intBitsToFloat'); - $encoder = array(AvroIOBinaryEncoder::class, 'floatToIntBits'); + $this->assert_encode_nan_values($type, $val, $bits); } - else + + public static function normal_vals_provider(): array { - $decoder = array(AvroIOBinaryDecoder::class, 'longBitsToDouble'); - $encoder = array(AvroIOBinaryEncoder::class, 'doubleToLongBits'); + $ruby_to_generate_vals = <<<_RUBY + def d2lb(d); [d].pack('E') end + dary = (-10..10).to_a + [-1234.2132, -211e23] + dary.each {|x| b = d2lb(x); puts %/array(self::DOUBLE_TYPE, (double) #{x}, #{b.inspect}, '#{b.unpack('h*')[0]}'),/} + def f2ib(f); [f].pack('e') end + fary = (-10..10).to_a + [-1234.5, -211.3e6] + fary.each {|x| b = f2ib(x); puts %/array(self::FLOAT_TYPE, (float) #{x}, #{b.inspect}, '#{b.unpack('h*')[0]}'),/} + _RUBY; + + return [ + [self::DOUBLE_TYPE, (float) -10, "\000\000\000\000\000\000$\300", '000000000000420c'], + [self::DOUBLE_TYPE, (float) -9, "\000\000\000\000\000\000\"\300", '000000000000220c'], + [self::DOUBLE_TYPE, (float) -8, "\000\000\000\000\000\000 \300", '000000000000020c'], + [self::DOUBLE_TYPE, (float) -7, "\000\000\000\000\000\000\034\300", '000000000000c10c'], + [self::DOUBLE_TYPE, (float) -6, "\000\000\000\000\000\000\030\300", '000000000000810c'], + [self::DOUBLE_TYPE, (float) -5, "\000\000\000\000\000\000\024\300", '000000000000410c'], + [self::DOUBLE_TYPE, (float) -4, "\000\000\000\000\000\000\020\300", '000000000000010c'], + [self::DOUBLE_TYPE, (float) -3, "\000\000\000\000\000\000\010\300", '000000000000800c'], + [self::DOUBLE_TYPE, (float) -2, "\000\000\000\000\000\000\000\300", '000000000000000c'], + [self::DOUBLE_TYPE, (float) -1, "\000\000\000\000\000\000\360\277", '0000000000000ffb'], + [self::DOUBLE_TYPE, (float) 0, "\000\000\000\000\000\000\000\000", '0000000000000000'], + [self::DOUBLE_TYPE, (float) 1, "\000\000\000\000\000\000\360?", '0000000000000ff3'], + [self::DOUBLE_TYPE, (float) 2, "\000\000\000\000\000\000\000@", '0000000000000004'], + [self::DOUBLE_TYPE, (float) 3, "\000\000\000\000\000\000\010@", '0000000000008004'], + [self::DOUBLE_TYPE, (float) 4, "\000\000\000\000\000\000\020@", '0000000000000104'], + [self::DOUBLE_TYPE, (float) 5, "\000\000\000\000\000\000\024@", '0000000000004104'], + [self::DOUBLE_TYPE, (float) 6, "\000\000\000\000\000\000\030@", '0000000000008104'], + [self::DOUBLE_TYPE, (float) 7, "\000\000\000\000\000\000\034@", '000000000000c104'], + [self::DOUBLE_TYPE, (float) 8, "\000\000\000\000\000\000 @", '0000000000000204'], + [self::DOUBLE_TYPE, (float) 9, "\000\000\000\000\000\000\"@", '0000000000002204'], + [self::DOUBLE_TYPE, (float) 10, "\000\000\000\000\000\000$@", '0000000000004204'], + [self::DOUBLE_TYPE, (float) -1234.2132, "\007\316\031Q\332H\223\300", '70ec9115ad84390c'], + [self::DOUBLE_TYPE, (float) -2.11e+25, "\311\260\276J\031t1\305", '9c0beba49147135c'], + + [self::FLOAT_TYPE, (float) -10, "\000\000 \301", '0000021c'], + [self::FLOAT_TYPE, (float) -9, "\000\000\020\301", '0000011c'], + [self::FLOAT_TYPE, (float) -8, "\000\000\000\301", '0000001c'], + [self::FLOAT_TYPE, (float) -7, "\000\000\340\300", '00000e0c'], + [self::FLOAT_TYPE, (float) -6, "\000\000\300\300", '00000c0c'], + [self::FLOAT_TYPE, (float) -5, "\000\000\240\300", '00000a0c'], + [self::FLOAT_TYPE, (float) -4, "\000\000\200\300", '0000080c'], + [self::FLOAT_TYPE, (float) -3, "\000\000@\300", '0000040c'], + [self::FLOAT_TYPE, (float) -2, "\000\000\000\300", '0000000c'], + [self::FLOAT_TYPE, (float) -1, "\000\000\200\277", '000008fb'], + [self::FLOAT_TYPE, (float) 0, "\000\000\000\000", '00000000'], + [self::FLOAT_TYPE, (float) 1, "\000\000\200?", '000008f3'], + [self::FLOAT_TYPE, (float) 2, "\000\000\000@", '00000004'], + [self::FLOAT_TYPE, (float) 3, "\000\000@@", '00000404'], + [self::FLOAT_TYPE, (float) 4, "\000\000\200@", '00000804'], + [self::FLOAT_TYPE, (float) 5, "\000\000\240@", '00000a04'], + [self::FLOAT_TYPE, (float) 6, "\000\000\300@", '00000c04'], + [self::FLOAT_TYPE, (float) 7, "\000\000\340@", '00000e04'], + [self::FLOAT_TYPE, (float) 8, "\000\000\000A", '00000014'], + [self::FLOAT_TYPE, (float) 9, "\000\000\020A", '00000114'], + [self::FLOAT_TYPE, (float) 10, "\000\000 A", '00000214'], + [self::FLOAT_TYPE, (float) -1234.5, "\000P\232\304", '0005a94c'], + [self::FLOAT_TYPE, (float) -211300000.0, "\352\202I\315", 'ae2894dc'], + ]; } - $decoded_bits_val = call_user_func($decoder, $bits); - $this->assertEquals($val, $decoded_bits_val, - sprintf("%s\n expected: '%f'\n given: '%f'", - 'DECODED BITS', $val, $decoded_bits_val)); - - $encoded_val_bits = call_user_func($encoder, $val); - $this->assertEquals($bits, $encoded_val_bits, - sprintf("%s\n expected: '%s'\n given: '%s'", - 'ENCODED VAL', - AvroDebug::hexString($bits), - AvroDebug::hexString($encoded_val_bits))); - - $round_trip_value = call_user_func($decoder, $encoded_val_bits); - $this->assertEquals($val, $round_trip_value, - sprintf("%s\n expected: '%f'\n given: '%f'", - 'ROUND TRIP BITS', $val, $round_trip_value)); - } - - function assert_encode_nan_values($type, $val, $bits) - { - if (self::FLOAT_TYPE == $type) + public static function float_vals_provider(): array { - $decoder = array(AvroIOBinaryDecoder::class, 'intBitsToFloat'); - $encoder = array(AvroIOBinaryEncoder::class, 'floatToIntBits'); + $array = []; + + foreach (self::normal_vals_provider() as $values) { + if (self::FLOAT_TYPE == $values[0]) { + $array[] = [$values[0], $values[1], $values[2]]; + } + } + + return $array; } - else + + public static function double_vals_provider(): array { - $decoder = array(AvroIOBinaryDecoder::class, 'longBitsToDouble'); - $encoder = array(AvroIOBinaryEncoder::class, 'doubleToLongBits'); + $array = []; + + foreach (self::normal_vals_provider() as $values) { + if (self::DOUBLE_TYPE === $values[0]) { + $array[] = [$values[0], $values[1], $values[2]]; + } + } + + return $array; } - $decoded_bits_val = call_user_func($decoder, $bits); - $this->assertTrue(is_nan($decoded_bits_val), - sprintf("%s\n expected: '%f'\n given: '%f'", - 'DECODED BITS', $val, $decoded_bits_val)); - - $encoded_val_bits = call_user_func($encoder, $val); - $this->assertEquals($bits, $encoded_val_bits, - sprintf("%s\n expected: '%s'\n given: '%s'", - 'ENCODED VAL', - AvroDebug::hexString($bits), - AvroDebug::hexString($encoded_val_bits))); - - $round_trip_value = call_user_func($decoder, $encoded_val_bits); - $this->assertTrue(is_nan($round_trip_value), - sprintf("%s\n expected: '%f'\n given: '%f'", - 'ROUND TRIP BITS', $val, $round_trip_value)); - } + #[DataProvider('float_vals_provider')] + public function test_encoding_float_values(string $type, $val, $bits): void + { + $this->assert_encode_values($type, $val, $bits); + } + + #[DataProvider('double_vals_provider')] + public function test_encoding_double_values($type, $val, $bits): void + { + $this->assert_encode_values($type, $val, $bits); + } + + public function assert_encode_values(string $type, $val, $bits): void + { + if (self::FLOAT_TYPE === $type) { + $decoder = [AvroIOBinaryDecoder::class, 'intBitsToFloat']; + $encoder = [AvroIOBinaryEncoder::class, 'floatToIntBits']; + } else { + $decoder = [AvroIOBinaryDecoder::class, 'longBitsToDouble']; + $encoder = [AvroIOBinaryEncoder::class, 'doubleToLongBits']; + } + + $decoded_bits_val = call_user_func($decoder, $bits); + $this->assertEquals( + $val, + $decoded_bits_val, + sprintf( + "%s\n expected: '%f'\n given: '%f'", + 'DECODED BITS', + $val, + $decoded_bits_val + ) + ); + + $encoded_val_bits = call_user_func($encoder, $val); + $this->assertEquals( + $bits, + $encoded_val_bits, + sprintf( + "%s\n expected: '%s'\n given: '%s'", + 'ENCODED VAL', + AvroDebug::hexString($bits), + AvroDebug::hexString($encoded_val_bits) + ) + ); + + $round_trip_value = call_user_func($decoder, $encoded_val_bits); + $this->assertEquals( + $val, + $round_trip_value, + sprintf( + "%s\n expected: '%f'\n given: '%f'", + 'ROUND TRIP BITS', + $val, + $round_trip_value + ) + ); + } + + public function assert_encode_nan_values(string $type, $val, $bits): void + { + if (self::FLOAT_TYPE === $type) { + $decoder = [AvroIOBinaryDecoder::class, 'intBitsToFloat']; + $encoder = [AvroIOBinaryEncoder::class, 'floatToIntBits']; + } else { + $decoder = [AvroIOBinaryDecoder::class, 'longBitsToDouble']; + $encoder = [AvroIOBinaryEncoder::class, 'doubleToLongBits']; + } + + $decoded_bits_val = call_user_func($decoder, $bits); + $this->assertTrue( + is_nan($decoded_bits_val), + sprintf( + "%s\n expected: '%f'\n given: '%f'", + 'DECODED BITS', + $val, + $decoded_bits_val + ) + ); + + $encoded_val_bits = call_user_func($encoder, $val); + $this->assertEquals( + $bits, + $encoded_val_bits, + sprintf( + "%s\n expected: '%s'\n given: '%s'", + 'ENCODED VAL', + AvroDebug::hexString($bits), + AvroDebug::hexString($encoded_val_bits) + ) + ); + + $round_trip_value = call_user_func($decoder, $encoded_val_bits); + $this->assertTrue( + is_nan($round_trip_value), + sprintf( + "%s\n expected: '%f'\n given: '%f'", + 'ROUND TRIP BITS', + $val, + $round_trip_value + ) + ); + } } diff --git a/lang/php/test/IODatumReaderTest.php b/lang/php/test/IODatumReaderTest.php index 0135751d80c..abb51507c3e 100644 --- a/lang/php/test/IODatumReaderTest.php +++ b/lang/php/test/IODatumReaderTest.php @@ -1,4 +1,5 @@ assertTrue(AvroIODatumReader::schemasMatch( - AvroSchema::parse($writers_schema), - AvroSchema::parse($readers_schema))); + AvroSchema::parse($writersSchema), + AvroSchema::parse($readersSchema) + )); } public function test_aliased(): void { $writers_schema = AvroSchema::parse( <<assertEquals(['field2' => 1], $record); } - public function testRecordNullField(): void + public function test_record_null_field(): void { $schema_json = << 1); + $datum = ["one" => 1]; $io = new AvroStringIO(); $writer = new AvroIODatumWriter($schema); @@ -103,25 +152,25 @@ public function testRecordNullField(): void $writer->write($datum, $encoder); $bin = $io->string(); - $this->assertSame('0200', bin2hex($bin)); + $this->assertSame('0200', bin2hex((string) $bin)); } - public function testRecordFieldWithDefault(): void + public function test_record_field_with_default(): void { $schema = AvroSchema::parse( <<assertEquals(['field1' => "foobar"], $record); } - public function testRecordWithLogicalTypes(): void + public function test_record_with_logical_types(): void { $schema = AvroSchema::parse( <<projection = AvroSchema::parse($this->projection_json); } - public function file_name_provider() + public static function file_name_provider(): array { $data_dir = AVRO_BUILD_DATA_DIR; - $data_files = array(); + $data_files = []; if (!($dh = opendir($data_dir))) { - die("Could not open data dir '$data_dir'\n"); + exit("Could not open data dir '$data_dir'\n"); } while ($file = readdir($dh)) { if (preg_match('/^[a-z]+(_deflate|_snappy|_zstandard|_bzip2)?\.avro$/', $file)) { - $data_files [] = implode(DIRECTORY_SEPARATOR, array($data_dir, $file)); - } else if (preg_match('/[^.]/', $file)) { + $data_files[] = implode(DIRECTORY_SEPARATOR, [$data_dir, $file]); + } elseif (preg_match('/[^.]/', $file)) { echo "Skipped: $data_dir/$file", PHP_EOL; } } closedir($dh); - $ary = array(); + $ary = []; foreach ($data_files as $df) { echo "Reading: $df", PHP_EOL; - $ary [] = array($df); + $ary[] = [$df]; } + return $ary; } - /** - * @coversNothing - * @dataProvider file_name_provider - */ - public function test_read($file_name) + #[DataProvider('file_name_provider')] + public function test_read($file_name): void { $dr = AvroDataIO::openFile( - $file_name, AvroFile::READ_MODE, $this->projection_json); + $file_name, + AvroFile::READ_MODE, + $this->projection_json + ); $data = $dr->data(); $this->assertNotCount(0, $data, sprintf("no data read from %s", $file_name)); - foreach ($data as $idx => $datum) { + foreach ($data as $datum) { $this->assertNotNull($datum, sprintf("null datum from %s", $file_name)); } } diff --git a/lang/php/test/LongEncodingTest.php b/lang/php/test/LongEncodingTest.php index da4e4c81674..7259c7f9fe3 100644 --- a/lang/php/test/LongEncodingTest.php +++ b/lang/php/test/LongEncodingTest.php @@ -1,4 +1,5 @@ skip_64_bit_test_on_32_bit(); - $lval = (int) ((int) $val << $shift); - $this->assert_bit_shift($expected_lval, strval($lval), - 'lshift', $lbin, decbin($lval)); + $lval = ((int) $val << $shift); + $this->assert_bit_shift( + expected: $expected_lval, + actual: (string) $lval, + shift_type: 'lshift', + expected_binary: $lbin, + actual_binary: decbin($lval) + ); $rval = ((int) $val >> $shift); - $this->assert_bit_shift($expected_rval, strval($rval), - 'rshift', $rbin, decbin($rval)); + $this->assert_bit_shift( + expected: $expected_rval, + actual: (string) $rval, + shift_type: 'rshift', + expected_binary: $rbin, + actual_binary: decbin($rval) + ); } - function skip_64_bit_test_on_32_bit() + public function skip_64_bit_test_on_32_bit(): void { if (!self::is_64_bit()) { $this->markTestSkipped('Requires 64-bit platform'); } } - static function is_64_bit() + public static function is_64_bit(): bool { - return (PHP_INT_SIZE == 8); + return PHP_INT_SIZE === 8; } - function assert_bit_shift( + public function assert_bit_shift( $expected, $actual, $shift_type, $expected_binary, $actual_binary - ) { + ): void { $this->assertEquals( - $expected, $actual, - sprintf("%s\nexpected: %d\n actual: %d\nexpected b: %s\n actual b: %s", - $shift_type, $expected, $actual, - $expected_binary, $actual_binary)); + $expected, + $actual, + sprintf( + "%s\nexpected: %d\n actual: %d\nexpected b: %s\n actual b: %s", + $shift_type, + $expected, + $actual, + $expected_binary, + $actual_binary + ) + ); } - /** - * @dataProvider bit_shift_provider - */ - function test_left_shift_gmp( - $val, - $shift, - $expected_lval, - $expected_rval, - $lbin, - $rbin - ) { + #[DataProvider('bit_shift_provider')] + public function test_left_shift_gmp( + string $val, + int $shift, + string $expected_lval, + string $expected_rval, + string $lbin, + string $rbin + ): void { $this->skip_if_no_gmp(); $lval = gmp_strval(AvroGMP::shiftLeft($val, $shift)); - $this->assert_bit_shift($expected_lval, $lval, 'gmp left shift', - $lbin, decbin((int) $lval)); + $this->assert_bit_shift( + $expected_lval, + $lval, + 'gmp left shift', + $lbin, + decbin((int) $lval) + ); } - function skip_if_no_gmp() + public function skip_if_no_gmp(): void { if (!extension_loaded('gmp')) { $this->markTestSkipped('Requires GMP PHP Extension.'); } } - /** - * @dataProvider bit_shift_provider - */ - function test_right_shift_gmp( - $val, - $shift, - $expected_lval, - $expected_rval, - $lbin, - $rbin - ) { + #[DataProvider('bit_shift_provider')] + public function test_right_shift_gmp( + string $val, + int $shift, + string $expected_lval, + string $expected_rval, + string $lbin, + string $rbin + ): void { $this->skip_if_no_gmp(); $rval = gmp_strval(AvroGMP::shiftRight($val, $shift)); - $this->assert_bit_shift($expected_rval, $rval, 'gmp right shift', - $rbin, decbin((int) $rval)); + $this->assert_bit_shift( + $expected_rval, + $rval, + 'gmp right shift', + $rbin, + decbin((int) $rval) + ); } - /** - * @dataProvider long_provider - */ - function test_encode_long($val, $expected_bytes) + #[DataProvider('long_provider')] + public function test_encode_long(string $val, string $expected_bytes): void { $this->skip_64_bit_test_on_32_bit(); $bytes = AvroIOBinaryEncoder::encodeLong($val); $this->assertEquals($expected_bytes, $bytes); } - /** - * @dataProvider long_provider - */ - function test_gmp_encode_long($val, $expected_bytes) + #[DataProvider('long_provider')] + public function test_gmp_encode_long(string $val, string $expected_bytes): void { $this->skip_if_no_gmp(); $bytes = AvroGMP::encodeLong($val); $this->assertEquals($expected_bytes, $bytes); } - /** - * @dataProvider long_provider - */ - function test_decode_long_from_array($expected_val, $bytes) + #[DataProvider('long_provider')] + public function test_decode_long_from_array(string $expected_val, string $bytes): void { $this->skip_64_bit_test_on_32_bit(); - $ary = array_map('ord', str_split($bytes)); + $ary = array_map(ord(...), str_split($bytes)); $val = AvroIOBinaryDecoder::decodeLongFromArray($ary); $this->assertEquals($expected_val, $val); } - /** - * @dataProvider long_provider - */ - function test_gmp_decode_long_from_array($expected_val, $bytes) + #[DataProvider('long_provider')] + public function test_gmp_decode_long_from_array(string $expected_val, string $bytes): void { $this->skip_if_no_gmp(); - $ary = array_map('ord', str_split($bytes)); + $ary = array_map(ord(...), str_split($bytes)); $val = AvroGMP::decodeLongFromArray($ary); $this->assertEquals($expected_val, $val); } - function long_provider() + public static function long_provider() { return [ ['0', "\x0"], @@ -170,12 +191,12 @@ function long_provider() ['-7', "\xd"], ['-10000', "\x9f\x9c\x1"], ['-2147483648', "\xff\xff\xff\xff\xf"], - ['-98765432109', "\xd9\x94\x87\xee\xdf\x5"] + ['-98765432109', "\xd9\x94\x87\xee\xdf\x5"], ]; } - function bit_shift_provider() + public static function bit_shift_provider() { // val shift lval rval return [ @@ -185,7 +206,7 @@ function bit_shift_provider() '0', '0', '0', - '0' + '0', ], [ '0', @@ -193,7 +214,7 @@ function bit_shift_provider() '0', '0', '0', - '0' + '0', ], [ '0', @@ -201,7 +222,7 @@ function bit_shift_provider() '0', '0', '0', - '0' + '0', ], [ '0', @@ -209,7 +230,7 @@ function bit_shift_provider() '0', '0', '0', - '0' + '0', ], [ '1', @@ -217,7 +238,7 @@ function bit_shift_provider() '1', '1', '1', - '1' + '1', ], [ '1', @@ -225,7 +246,7 @@ function bit_shift_provider() '2', '0', '10', - '0' + '0', ], [ '1', @@ -233,7 +254,7 @@ function bit_shift_provider() '128', '0', '10000000', - '0' + '0', ], [ '1', @@ -241,7 +262,7 @@ function bit_shift_provider() '-9223372036854775808', '0', '1000000000000000000000000000000000000000000000000000000000000000', - '0' + '0', ], [ '100', @@ -249,7 +270,7 @@ function bit_shift_provider() '100', '100', '1100100', - '1100100' + '1100100', ], [ '100', @@ -257,7 +278,7 @@ function bit_shift_provider() '200', '50', '11001000', - '110010' + '110010', ], [ '100', @@ -265,7 +286,7 @@ function bit_shift_provider() '12800', '0', '11001000000000', - '0' + '0', ], [ '100', @@ -273,7 +294,7 @@ function bit_shift_provider() '0', '0', '0', - '0' + '0', ], [ '1000000', @@ -281,7 +302,7 @@ function bit_shift_provider() '1000000', '1000000', '11110100001001000000', - '11110100001001000000' + '11110100001001000000', ], [ '1000000', @@ -289,7 +310,7 @@ function bit_shift_provider() '2000000', '500000', '111101000010010000000', - '1111010000100100000' + '1111010000100100000', ], [ '1000000', @@ -297,7 +318,7 @@ function bit_shift_provider() '128000000', '7812', '111101000010010000000000000', - '1111010000100' + '1111010000100', ], [ '1000000', @@ -305,7 +326,7 @@ function bit_shift_provider() '0', '0', '0', - '0' + '0', ], [ '2147483647', @@ -313,7 +334,7 @@ function bit_shift_provider() '2147483647', '2147483647', '1111111111111111111111111111111', - '1111111111111111111111111111111' + '1111111111111111111111111111111', ], [ '2147483647', @@ -321,7 +342,7 @@ function bit_shift_provider() '4294967294', '1073741823', '11111111111111111111111111111110', - '111111111111111111111111111111' + '111111111111111111111111111111', ], [ '2147483647', @@ -329,7 +350,7 @@ function bit_shift_provider() '274877906816', '16777215', '11111111111111111111111111111110000000', - '111111111111111111111111' + '111111111111111111111111', ], [ '2147483647', @@ -337,7 +358,7 @@ function bit_shift_provider() '-9223372036854775808', '0', '1000000000000000000000000000000000000000000000000000000000000000', - '0' + '0', ], [ '10000000000', @@ -345,7 +366,7 @@ function bit_shift_provider() '10000000000', '10000000000', '1001010100000010111110010000000000', - '1001010100000010111110010000000000' + '1001010100000010111110010000000000', ], [ '10000000000', @@ -353,7 +374,7 @@ function bit_shift_provider() '20000000000', '5000000000', '10010101000000101111100100000000000', - '100101010000001011111001000000000' + '100101010000001011111001000000000', ], [ '10000000000', @@ -361,7 +382,7 @@ function bit_shift_provider() '1280000000000', '78125000', '10010101000000101111100100000000000000000', - '100101010000001011111001000' + '100101010000001011111001000', ], [ '10000000000', @@ -369,7 +390,7 @@ function bit_shift_provider() '0', '0', '0', - '0' + '0', ], [ '9223372036854775807', @@ -377,7 +398,7 @@ function bit_shift_provider() '9223372036854775807', '9223372036854775807', '111111111111111111111111111111111111111111111111111111111111111', - '111111111111111111111111111111111111111111111111111111111111111' + '111111111111111111111111111111111111111111111111111111111111111', ], [ '9223372036854775807', @@ -385,7 +406,7 @@ function bit_shift_provider() '-2', '4611686018427387903', '1111111111111111111111111111111111111111111111111111111111111110', - '11111111111111111111111111111111111111111111111111111111111111' + '11111111111111111111111111111111111111111111111111111111111111', ], [ '9223372036854775807', @@ -393,7 +414,7 @@ function bit_shift_provider() '-128', '72057594037927935', '1111111111111111111111111111111111111111111111111111111110000000', - '11111111111111111111111111111111111111111111111111111111' + '11111111111111111111111111111111111111111111111111111111', ], [ '9223372036854775807', @@ -401,7 +422,7 @@ function bit_shift_provider() '-9223372036854775808', '0', '1000000000000000000000000000000000000000000000000000000000000000', - '0' + '0', ], [ '-1', @@ -409,7 +430,7 @@ function bit_shift_provider() '-1', '-1', '1111111111111111111111111111111111111111111111111111111111111111', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-1', @@ -417,7 +438,7 @@ function bit_shift_provider() '-2', '-1', '1111111111111111111111111111111111111111111111111111111111111110', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-1', @@ -425,7 +446,7 @@ function bit_shift_provider() '-128', '-1', '1111111111111111111111111111111111111111111111111111111110000000', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-1', @@ -433,7 +454,7 @@ function bit_shift_provider() '-9223372036854775808', '-1', '1000000000000000000000000000000000000000000000000000000000000000', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-100', @@ -441,7 +462,7 @@ function bit_shift_provider() '-100', '-100', '1111111111111111111111111111111111111111111111111111111110011100', - '1111111111111111111111111111111111111111111111111111111110011100' + '1111111111111111111111111111111111111111111111111111111110011100', ], [ '-100', @@ -449,7 +470,7 @@ function bit_shift_provider() '-200', '-50', '1111111111111111111111111111111111111111111111111111111100111000', - '1111111111111111111111111111111111111111111111111111111111001110' + '1111111111111111111111111111111111111111111111111111111111001110', ], [ '-100', @@ -457,7 +478,7 @@ function bit_shift_provider() '-12800', '-1', '1111111111111111111111111111111111111111111111111100111000000000', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-100', @@ -465,7 +486,7 @@ function bit_shift_provider() '0', '-1', '0', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-1000000', @@ -473,7 +494,7 @@ function bit_shift_provider() '-1000000', '-1000000', '1111111111111111111111111111111111111111111100001011110111000000', - '1111111111111111111111111111111111111111111100001011110111000000' + '1111111111111111111111111111111111111111111100001011110111000000', ], [ '-1000000', @@ -481,7 +502,7 @@ function bit_shift_provider() '-2000000', '-500000', '1111111111111111111111111111111111111111111000010111101110000000', - '1111111111111111111111111111111111111111111110000101111011100000' + '1111111111111111111111111111111111111111111110000101111011100000', ], [ '-1000000', @@ -489,7 +510,7 @@ function bit_shift_provider() '-128000000', '-7813', '1111111111111111111111111111111111111000010111101110000000000000', - '1111111111111111111111111111111111111111111111111110000101111011' + '1111111111111111111111111111111111111111111111111110000101111011', ], [ '-1000000', @@ -497,7 +518,7 @@ function bit_shift_provider() '0', '-1', '0', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-2147483648', @@ -505,7 +526,7 @@ function bit_shift_provider() '-2147483648', '-2147483648', '1111111111111111111111111111111110000000000000000000000000000000', - '1111111111111111111111111111111110000000000000000000000000000000' + '1111111111111111111111111111111110000000000000000000000000000000', ], [ '-2147483648', @@ -513,7 +534,7 @@ function bit_shift_provider() '-4294967296', '-1073741824', '1111111111111111111111111111111100000000000000000000000000000000', - '1111111111111111111111111111111111000000000000000000000000000000' + '1111111111111111111111111111111111000000000000000000000000000000', ], [ '-2147483648', @@ -521,7 +542,7 @@ function bit_shift_provider() '-274877906944', '-16777216', '1111111111111111111111111100000000000000000000000000000000000000', - '1111111111111111111111111111111111111111000000000000000000000000' + '1111111111111111111111111111111111111111000000000000000000000000', ], [ '-2147483648', @@ -529,7 +550,7 @@ function bit_shift_provider() '0', '-1', '0', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-10000000000', @@ -537,7 +558,7 @@ function bit_shift_provider() '-10000000000', '-10000000000', '1111111111111111111111111111110110101011111101000001110000000000', - '1111111111111111111111111111110110101011111101000001110000000000' + '1111111111111111111111111111110110101011111101000001110000000000', ], [ '-10000000000', @@ -545,7 +566,7 @@ function bit_shift_provider() '-20000000000', '-5000000000', '1111111111111111111111111111101101010111111010000011100000000000', - '1111111111111111111111111111111011010101111110100000111000000000' + '1111111111111111111111111111111011010101111110100000111000000000', ], [ '-10000000000', @@ -553,7 +574,7 @@ function bit_shift_provider() '-1280000000000', '-78125000', '1111111111111111111111101101010111111010000011100000000000000000', - '1111111111111111111111111111111111111011010101111110100000111000' + '1111111111111111111111111111111111111011010101111110100000111000', ], [ '-10000000000', @@ -561,7 +582,7 @@ function bit_shift_provider() '0', '-1', '0', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], [ '-9223372036854775808', @@ -569,7 +590,7 @@ function bit_shift_provider() '-9223372036854775808', '-9223372036854775808', '1000000000000000000000000000000000000000000000000000000000000000', - '1000000000000000000000000000000000000000000000000000000000000000' + '1000000000000000000000000000000000000000000000000000000000000000', ], [ '-9223372036854775808', @@ -577,7 +598,7 @@ function bit_shift_provider() '0', '-4611686018427387904', '0', - '1100000000000000000000000000000000000000000000000000000000000000' + '1100000000000000000000000000000000000000000000000000000000000000', ], [ '-9223372036854775808', @@ -585,7 +606,7 @@ function bit_shift_provider() '0', '-72057594037927936', '0', - '1111111100000000000000000000000000000000000000000000000000000000' + '1111111100000000000000000000000000000000000000000000000000000000', ], [ '-9223372036854775808', @@ -593,7 +614,7 @@ function bit_shift_provider() '0', '-1', '0', - '1111111111111111111111111111111111111111111111111111111111111111' + '1111111111111111111111111111111111111111111111111111111111111111', ], ]; } diff --git a/lang/php/test/NameTest.php b/lang/php/test/NameTest.php index 7ecc0d8f582..059b0976808 100644 --- a/lang/php/test/NameTest.php +++ b/lang/php/test/NameTest.php @@ -1,4 +1,5 @@ name = $name; - $this->namespace = $namespace; - $this->default_namespace = $default_namespace; - $this->is_valid = $is_valid; - $this->expected_fullname = $expected_fullname; + public function __construct(public $name, public $namespace, public $default_namespace, public $is_valid, public $expected_fullname = null) + { } - function __toString() + public function __toString(): string { return var_export($this, true); } @@ -53,10 +39,9 @@ function __toString() class NameTest extends TestCase { - - function fullname_provider() + public static function fullname_provider(): array { - $examples = array( + $examples = [ new NameExample('foo', null, null, true, 'foo'), new NameExample('foo', 'bar', null, true, 'bar.foo'), new NameExample('bar.foo', 'baz', null, true, 'bar.foo'), @@ -68,32 +53,33 @@ function fullname_provider() new NameExample('bar.f0o', 'baz', null, true, 'bar.f0o'), new NameExample(' .foo', 'baz', null, false), new NameExample('bar. foo', 'baz', null, false), - new NameExample('bar. ', 'baz', null, false) - ); - $exes = array(); + new NameExample('bar. ', 'baz', null, false), + ]; + $exes = []; foreach ($examples as $ex) { - $exes [] = array($ex); + $exes[] = [$ex]; } + return $exes; } - /** - * @dataProvider fullname_provider - */ - function test_fullname($ex) + #[DataProvider('fullname_provider')] + public function test_fullname($ex): void { try { $name = new AvroName($ex->name, $ex->namespace, $ex->default_namespace); $this->assertTrue($ex->is_valid); $this->assertEquals($ex->expected_fullname, $name->fullname()); } catch (AvroSchemaParseException $e) { - $this->assertFalse($ex->is_valid, sprintf("%s:\n%s", + $this->assertFalse($ex->is_valid, sprintf( + "%s:\n%s", $ex, - $e->getMessage())); + $e->getMessage() + )); } } - function name_provider() + public static function name_provider(): array { return [ ['a', true], @@ -102,14 +88,12 @@ function name_provider() ['', false], [null, false], [' ', false], - ['Cons', true] + ['Cons', true], ]; } - /** - * @dataProvider name_provider - */ - function test_name($name, $is_well_formed) + #[DataProvider('name_provider')] + public function test_name($name, $is_well_formed): void { $this->assertEquals(AvroName::isWellFormedName($name), $is_well_formed); } diff --git a/lang/php/test/ProtocolFileTest.php b/lang/php/test/ProtocolFileTest.php index fd24cc32b46..fcb276fee63 100644 --- a/lang/php/test/ProtocolFileTest.php +++ b/lang/php/test/ProtocolFileTest.php @@ -1,4 +1,5 @@ prot_parseable); - for ($i = 0; $i < $cnt; $i++) { - try { - //print($i . " " . ($this->prot_parseable[$i]?"true":"false") . " \n"); - $prot = AvroProtocol::parse($this->prot_data[$i]); - } catch (AvroSchemaParseException $x) { - // exception ok if we expected this protocol spec to be unparseable - $this->assertEquals(false, $this->prot_parseable[$i]); - } + try { + AvroProtocol::parse($jsonSchema); + self::assertTrue($expectedResult); + } catch (AvroSchemaParseException) { + // exception ok if we expected this protocol spec to be unparseable + self::assertFalse($expectedResult); } } } diff --git a/lang/php/test/SchemaTest.php b/lang/php/test/SchemaTest.php index e0d7be75b07..e1ab5a10adb 100644 --- a/lang/php/test/SchemaTest.php +++ b/lang/php/test/SchemaTest.php @@ -1,4 +1,5 @@ schema_string = $schema_string; - $this->is_valid = $is_valid; - $this->name = $name ? $name : $schema_string; - $this->normalized_schema_string = $normalized_schema_string - ? $normalized_schema_string : json_encode(json_decode($schema_string, true)); - $this->comment = $comment; + $this->name = $name ?: $this->schema_string; + $this->normalized_schema_string = $normalized_schema_string ?: json_encode(json_decode((string) $this->schema_string, true)); } } @@ -61,8 +56,10 @@ public function test_json_decode(): void $this->assertEquals(["foo" => 27], (array) json_decode('{"foo": 27}')); $this->assertIsArray(json_decode('{"foo": 27}', true)); $this->assertEquals(["foo" => 27], json_decode('{"foo": 27}', true)); - $this->assertEquals(["bar", "baz", "blurfl"], - json_decode('["bar", "baz", "blurfl"]', true)); + $this->assertEquals( + ["bar", "baz", "blurfl"], + json_decode('["bar", "baz", "blurfl"]', true) + ); $this->assertIsNotArray(json_decode('null', true)); $this->assertEquals(["type" => 'null'], json_decode('{"type": "null"}', true)); @@ -73,31 +70,325 @@ public function test_json_decode(): void $this->assertEquals('boolean', json_decode('"boolean"')); } - public function schema_examples_provider(): array + public static function schema_examples_provider(): array { self::make_examples(); $ary = []; foreach (self::$examples as $example) { $ary[] = [$example]; } + return $ary; } + #[DataProvider('schema_examples_provider')] + public function test_parse($example): void + { + $schema_string = $example->schema_string; + + try { + $normalized_schema_string = $example->normalized_schema_string; + $schema = AvroSchema::parse($schema_string); + $this->assertTrue( + $example->is_valid, + sprintf( + "schema_string: %s\n", + $schema_string + ) + ); + $this->assertEquals($normalized_schema_string, (string) $schema); + } catch (AvroSchemaParseException $e) { + $this->assertFalse( + $example->is_valid, + sprintf( + "schema_string: %s\n%s", + $schema_string, + $e->getMessage() + ) + ); + } + } + + public function test_to_avro_includes_aliases(): void + { + $hash = <<assertEquals($schema->toAvro(), json_decode($hash, true)); + } + + public function test_validate_field_aliases(): void + { + $this->expectException(AvroSchemaParseException::class); + $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.'); + AvroSchema::parse( + <<expectException(AvroSchemaParseException::class); + $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.'); + AvroSchema::parse( + <<expectException(AvroSchemaParseException::class); + $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.'); + AvroSchema::parse( + <<expectException(AvroSchemaParseException::class); + $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.'); + AvroSchema::parse(<<expectException(AvroSchemaParseException::class); + $this->expectExceptionMessage('Alias already in use'); + AvroSchema::parse(<<expectNotToPerformAssertions(); + AvroSchema::parse( + <<toAvro(), json_decode($avro, true)); + } + + public static function invalidDecimalLogicalTypeDataProvider(): array + { + return [ + 'bytes - invalid precision' => [ + '{"type": "bytes", "logicalType": "decimal", "precision": -1, "scale": 2}', + new AvroException("Precision '-1' is invalid. It must be a positive integer."), + ], + 'bytes - invalid value for precision (float)' => [ + '{"type": "bytes", "logicalType": "decimal", "precision": 11.23, "scale": 2}', + new AvroException("Invalid value '11.23' for 'precision' attribute of decimal logical type."), + ], + 'bytes - invalid value for precision (string)' => [ + '{"type": "bytes", "logicalType": "decimal", "precision": "banana", "scale": 2}', + new AvroException("Invalid value 'banana' for 'precision' attribute of decimal logical type."), + ], + 'bytes - invalid scale' => [ + '{"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": -1}', + new AvroException("Scale '-1' is invalid. It must be a non-negative integer."), + ], + 'bytes - invalid scale for precision' => [ + '{"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": 2}', + new AvroException("Scale must be a lower than precision (scale='2', precision='2')."), + ], + 'bytes - invalid value for scale (float)' => [ + '{"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": 9.12}', + new AvroException("Invalid value '9.12' for 'scale' attribute of decimal logical type."), + ], + 'bytes - invalid value for scale (string)' => [ + '{"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": "two"}', + new AvroException("Invalid value 'two' for 'scale' attribute of decimal logical type."), + ], + 'fixed - invalid precision' => [ + '{"name": "fixed_decimal", "type": "fixed", "logicalType": "decimal", "size": 2, "precision": -1, "scale": 2}', + new AvroException("Precision '-1' is invalid. It must be a positive integer."), + ], + 'fixed - invalid value for precision with specified size' => [ + '{"name": "fixed_decimal", "type": "fixed", "logicalType": "decimal", "size": 2, "precision": 6, "scale": 2}', + new AvroException("Invalid precision for specified fixed size (size='2', precision='6')."), + ], + ]; + } + + #[DataProvider('invalidDecimalLogicalTypeDataProvider')] + public function test_decimal_logical_type_with_invalid_parameters(string $schema, AvroException $exception): void + { + $this->expectException($exception::class); + $this->expectExceptionMessage($exception->getMessage()); + AvroSchema::parse($schema); + } + protected static function make_examples(): void { - $primitive_examples = array_merge([ - new SchemaExample('"True"', false), - new SchemaExample('{"no_type": "test"}', false), - new SchemaExample('{"type": "panther"}', false) - ], - self::make_primitive_examples()); + $primitive_examples = array_merge( + [ + new SchemaExample('"True"', false), + new SchemaExample('{"no_type": "test"}', false), + new SchemaExample('{"type": "panther"}', false), + ], + self::make_primitive_examples() + ); $array_examples = [ new SchemaExample('{"type": "array", "items": "long"}', true), new SchemaExample(' {"type": "array", "items": {"type": "enum", "name": "Test", "symbols": ["A", "B"]}} - ', true) + ', true), ]; $map_examples = [ @@ -105,7 +396,7 @@ protected static function make_examples(): void new SchemaExample(' {"type": "map", "values": {"type": "enum", "name": "Test", "symbols": ["A", "B"]}} - ', true) + ', true), ]; $union_examples = [ @@ -129,7 +420,8 @@ protected static function make_examples(): void new SchemaExample('["long", ["string", "null"], "int"]', false), - new SchemaExample('["null", "boolean", "int", "long", "float", "double", + new SchemaExample( + '["null", "boolean", "int", "long", "float", "double", "string", "bytes", {"type": "array", "items":"int"}, {"type": "map", "values":"int"}, @@ -138,9 +430,12 @@ protected static function make_examples(): void {"name": "foo", "type":"fixed", "size":16}, {"name": "baz", "type":"enum", "symbols":["A", "B", "C"]} - ]', true, - '["null","boolean","int","long","float","double","string","bytes",{"type":"array","items":"int"},{"type":"map","values":"int"},{"type":"record","name":"bar","fields":[{"name":"label","type":"string"}]},{"type":"fixed","name":"foo","size":16},{"type":"enum","name":"baz","symbols":["A","B","C"]}]'), - new SchemaExample(' + ]', + true, + '["null","boolean","int","long","float","double","string","bytes",{"type":"array","items":"int"},{"type":"map","values":"int"},{"type":"record","name":"bar","fields":[{"name":"label","type":"string"}]},{"type":"fixed","name":"foo","size":16},{"type":"enum","name":"baz","symbols":["A","B","C"]}]' + ), + new SchemaExample( + ' [{"name":"subtract", "namespace":"com.example", "type":"record", "fields":[{"name":"minuend", "type":"int"}, @@ -150,8 +445,10 @@ protected static function make_examples(): void "fields":[{"name":"quotient", "type":"int"}, {"name":"dividend", "type":"int"}]}, {"type": "array", "items": "string"}] - ', true, - '[{"type":"record","name":"subtract","namespace":"com.example","fields":[{"name":"minuend","type":"int"},{"name":"subtrahend","type":"int"}]},{"type":"record","name":"divide","namespace":"com.example","fields":[{"name":"quotient","type":"int"},{"name":"dividend","type":"int"}]},{"type":"array","items":"string"}]'), + ', + true, + '[{"type":"record","name":"subtract","namespace":"com.example","fields":[{"name":"minuend","type":"int"},{"name":"subtrahend","type":"int"}]},{"type":"record","name":"divide","namespace":"com.example","fields":[{"name":"quotient","type":"int"},{"name":"dividend","type":"int"}]},{"type":"array","items":"string"}]' + ), ]; $fixed_examples = [ @@ -170,32 +467,48 @@ protected static function make_examples(): void {"type": "fixed", "size": 314} ', false), - new SchemaExample('{"type":"fixed","name":"ex","doc":"this should be ignored","size": 314}', + new SchemaExample( + '{"type":"fixed","name":"ex","doc":"this should be ignored","size": 314}', true, - '{"type":"fixed","name":"ex","size":314}'), - new SchemaExample('{"name": "bar", + '{"type":"fixed","name":"ex","size":314}' + ), + new SchemaExample( + '{"name": "bar", "namespace": "com.example", "type": "fixed", - "size": 32 }', true, - '{"type":"fixed","name":"bar","namespace":"com.example","size":32}'), - new SchemaExample('{"name": "com.example.bar", + "size": 32 }', + true, + '{"type":"fixed","name":"bar","namespace":"com.example","size":32}' + ), + new SchemaExample( + '{"name": "com.example.bar", "type": "fixed", - "size": 32 }', true, - '{"type":"fixed","name":"bar","namespace":"com.example","size":32}') + "size": 32 }', + true, + '{"type":"fixed","name":"bar","namespace":"com.example","size":32}' + ), ]; - $fixed_examples [] = new SchemaExample( - '{"type":"fixed","name":"_x.bar","size":4}', true, - '{"type":"fixed","name":"bar","namespace":"_x","size":4}'); - $fixed_examples [] = new SchemaExample( - '{"type":"fixed","name":"baz._x","size":4}', true, - '{"type":"fixed","name":"_x","namespace":"baz","size":4}'); - $fixed_examples [] = new SchemaExample( - '{"type":"fixed","name":"baz.3x","size":4}', false); $fixed_examples[] = new SchemaExample( - '{"type":"fixed", "name":"Fixed2", "aliases":["Fixed1"], "size": 2}', true); + '{"type":"fixed","name":"_x.bar","size":4}', + true, + '{"type":"fixed","name":"bar","namespace":"_x","size":4}' + ); + $fixed_examples[] = new SchemaExample( + '{"type":"fixed","name":"baz._x","size":4}', + true, + '{"type":"fixed","name":"_x","namespace":"baz","size":4}' + ); + $fixed_examples[] = new SchemaExample( + '{"type":"fixed","name":"baz.3x","size":4}', + false + ); + $fixed_examples[] = new SchemaExample( + '{"type":"fixed", "name":"Fixed2", "aliases":["Fixed1"], "size": 2}', + true + ); - $enum_examples = array( + $enum_examples = [ new SchemaExample('{"type": "enum", "name": "Test", "symbols": ["A", "B"]}', true), new SchemaExample(' {"type": "enum", @@ -216,8 +529,10 @@ protected static function make_examples(): void "name": "Test" "symbols" : ["AA", "AA"]} ', false), - new SchemaExample('{"type":"enum","name":"Test","symbols":["AA", 16]}', - false), + new SchemaExample( + '{"type":"enum","name":"Test","symbols":["AA", 16]}', + false + ), new SchemaExample(' {"type": "enum", "name": "blood_types", @@ -229,43 +544,42 @@ protected static function make_examples(): void "name": "blood-types", "doc": 16, "symbols" : ["A", "AB", "B", "O"]} - ', false) - ); - + ', false), + ]; - $record_examples = array(); - $record_examples [] = new SchemaExample(' + $record_examples = []; + $record_examples[] = new SchemaExample(' {"type": "record", "name": "Test", "fields": [{"name": "f", "type": "long"}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "error", "name": "Test", "fields": [{"name": "f", "type": "long"}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "name": "Node", "fields": [{"name": "label", "type": "string"}, {"name": "children", "type": {"type": "array", "items": "Node"}}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "name": "ListLink", "fields": [{"name": "car", "type": "int"}, {"name": "cdr", "type": "ListLink"}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "name": "Lisp", "fields": [{"name": "value", "type": ["null", "string"]}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "name": "Lisp", "fields": [{"name": "value", @@ -275,7 +589,7 @@ protected static function make_examples(): void "fields": [{"name": "car", "type": "string"}, {"name": "cdr", "type": "string"}]}]}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "name": "Lisp", "fields": [{"name": "value", @@ -285,7 +599,7 @@ protected static function make_examples(): void "fields": [{"name": "car", "type": "Lisp"}, {"name": "cdr", "type": "Lisp"}]}]}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "name": "HandshakeRequest", "namespace": "org.apache.avro.ipc", @@ -294,7 +608,7 @@ protected static function make_examples(): void {"name": "meta", "type": ["null", {"type": "map", "values": "bytes"}]}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "name": "HandshakeRequest", "namespace": "org.apache.avro.ipc", @@ -305,7 +619,8 @@ protected static function make_examples(): void {"name": "meta", "type": ["null", {"type": "map", "values": "bytes"}]}]} ', true); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample( + ' {"type": "record", "name": "HandshakeResponse", "namespace": "org.apache.avro.ipc", @@ -319,10 +634,12 @@ protected static function make_examples(): void {"name": "MD5", "size": 16, "type": "fixed"}]}, {"name": "meta", "type": ["null", {"type": "map", "values": "bytes"}]}]} - ', true, + ', + true, '{"type":"record","name":"HandshakeResponse","namespace":"org.apache.avro.ipc","fields":[{"name":"match","type":{"type":"enum","name":"HandshakeMatch","symbols":["BOTH","CLIENT","NONE"]}},{"name":"serverProtocol","type":["null","string"]},{"name":"serverHash","type":["null",{"type":"fixed","name":"MD5","size":16}]},{"name":"meta","type":["null",{"type":"map","values":"bytes"}]}]}' ); - $record_examples [] = new SchemaExample('{"type": "record", + $record_examples[] = new SchemaExample( + '{"type": "record", "namespace": "org.apache.avro", "name": "Interop", "fields": [{"type": {"fields": [{"type": {"items": "org.apache.avro.Node", @@ -331,9 +648,12 @@ protected static function make_examples(): void "type": "record", "name": "Node"}, "name": "recordField"}]} -', true, - '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"children","type":{"type":"array","items":"Node"}}]}}]}'); - $record_examples [] = new SchemaExample('{"type": "record", +', + true, + '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"children","type":{"type":"array","items":"Node"}}]}}]}' + ); + $record_examples[] = new SchemaExample( + '{"type": "record", "namespace": "org.apache.avro", "name": "Interop", "fields": [{"type": {"symbols": ["A", "B", "C"], "type": "enum", "name": "Kind"}, @@ -343,10 +663,13 @@ protected static function make_examples(): void "name": "children"}], "type": "record", "name": "Node"}, - "name": "recordField"}]}', true, - '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":"string"},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}'); + "name": "recordField"}]}', + true, + '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":"string"},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}' + ); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample( + ' {"type": "record", "name": "Interop", "namespace": "org.apache.avro", @@ -383,65 +706,79 @@ protected static function make_examples(): void {"name": "children", "type": {"type": "array", "items": "Node"}}]}}]} - ', true, - '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"intField","type":"int"},{"name":"longField","type":"long"},{"name":"stringField","type":"string"},{"name":"boolField","type":"boolean"},{"name":"floatField","type":"float"},{"name":"doubleField","type":"double"},{"name":"bytesField","type":"bytes"},{"name":"nullField","type":"null"},{"name":"arrayField","type":{"type":"array","items":"double"}},{"name":"mapField","type":{"type":"map","values":{"type":"record","name":"Foo","fields":[{"name":"label","type":"string"}]}}},{"name":"unionField","type":["boolean","double",{"type":"array","items":"bytes"}]},{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"fixedField","type":{"type":"fixed","name":"MD5","size":16}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":"string"},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}'); - $record_examples [] = new SchemaExample('{"type": "record", "namespace": "org.apache.avro", "name": "Interop", "fields": [{"type": "int", "name": "intField"}, {"type": "long", "name": "longField"}, {"type": "string", "name": "stringField"}, {"type": "boolean", "name": "boolField"}, {"type": "float", "name": "floatField"}, {"type": "double", "name": "doubleField"}, {"type": "bytes", "name": "bytesField"}, {"type": "null", "name": "nullField"}, {"type": {"items": "double", "type": "array"}, "name": "arrayField"}, {"type": {"type": "map", "values": {"fields": [{"type": "string", "name": "label"}], "type": "record", "name": "Foo"}}, "name": "mapField"}, {"type": ["boolean", "double", {"items": "bytes", "type": "array"}], "name": "unionField"}, {"type": {"symbols": ["A", "B", "C"], "type": "enum", "name": "Kind"}, "name": "enumField"}, {"type": {"type": "fixed", "name": "MD5", "size": 16}, "name": "fixedField"}, {"type": {"fields": [{"type": "string", "name": "label"}, {"type": {"items": "org.apache.avro.Node", "type": "array"}, "name": "children"}], "type": "record", "name": "Node"}, "name": "recordField"}]} -', true, - '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"intField","type":"int"},{"name":"longField","type":"long"},{"name":"stringField","type":"string"},{"name":"boolField","type":"boolean"},{"name":"floatField","type":"float"},{"name":"doubleField","type":"double"},{"name":"bytesField","type":"bytes"},{"name":"nullField","type":"null"},{"name":"arrayField","type":{"type":"array","items":"double"}},{"name":"mapField","type":{"type":"map","values":{"type":"record","name":"Foo","fields":[{"name":"label","type":"string"}]}}},{"name":"unionField","type":["boolean","double",{"type":"array","items":"bytes"}]},{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"fixedField","type":{"type":"fixed","name":"MD5","size":16}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":"string"},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}'); - $record_examples [] = new SchemaExample(' + ', + true, + '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"intField","type":"int"},{"name":"longField","type":"long"},{"name":"stringField","type":"string"},{"name":"boolField","type":"boolean"},{"name":"floatField","type":"float"},{"name":"doubleField","type":"double"},{"name":"bytesField","type":"bytes"},{"name":"nullField","type":"null"},{"name":"arrayField","type":{"type":"array","items":"double"}},{"name":"mapField","type":{"type":"map","values":{"type":"record","name":"Foo","fields":[{"name":"label","type":"string"}]}}},{"name":"unionField","type":["boolean","double",{"type":"array","items":"bytes"}]},{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"fixedField","type":{"type":"fixed","name":"MD5","size":16}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":"string"},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}' + ); + $record_examples[] = new SchemaExample( + '{"type": "record", "namespace": "org.apache.avro", "name": "Interop", "fields": [{"type": "int", "name": "intField"}, {"type": "long", "name": "longField"}, {"type": "string", "name": "stringField"}, {"type": "boolean", "name": "boolField"}, {"type": "float", "name": "floatField"}, {"type": "double", "name": "doubleField"}, {"type": "bytes", "name": "bytesField"}, {"type": "null", "name": "nullField"}, {"type": {"items": "double", "type": "array"}, "name": "arrayField"}, {"type": {"type": "map", "values": {"fields": [{"type": "string", "name": "label"}], "type": "record", "name": "Foo"}}, "name": "mapField"}, {"type": ["boolean", "double", {"items": "bytes", "type": "array"}], "name": "unionField"}, {"type": {"symbols": ["A", "B", "C"], "type": "enum", "name": "Kind"}, "name": "enumField"}, {"type": {"type": "fixed", "name": "MD5", "size": 16}, "name": "fixedField"}, {"type": {"fields": [{"type": "string", "name": "label"}, {"type": {"items": "org.apache.avro.Node", "type": "array"}, "name": "children"}], "type": "record", "name": "Node"}, "name": "recordField"}]} +', + true, + '{"type":"record","name":"Interop","namespace":"org.apache.avro","fields":[{"name":"intField","type":"int"},{"name":"longField","type":"long"},{"name":"stringField","type":"string"},{"name":"boolField","type":"boolean"},{"name":"floatField","type":"float"},{"name":"doubleField","type":"double"},{"name":"bytesField","type":"bytes"},{"name":"nullField","type":"null"},{"name":"arrayField","type":{"type":"array","items":"double"}},{"name":"mapField","type":{"type":"map","values":{"type":"record","name":"Foo","fields":[{"name":"label","type":"string"}]}}},{"name":"unionField","type":["boolean","double",{"type":"array","items":"bytes"}]},{"name":"enumField","type":{"type":"enum","name":"Kind","symbols":["A","B","C"]}},{"name":"fixedField","type":{"type":"fixed","name":"MD5","size":16}},{"name":"recordField","type":{"type":"record","name":"Node","fields":[{"name":"label","type":"string"},{"name":"children","type":{"type":"array","items":"Node"}}]}}]}' + ); + $record_examples[] = new SchemaExample( + ' {"type": "record", "name": "ipAddr", "fields": [{"name": "addr", "type": [{"name": "IPv6", "type": "fixed", "size": 16}, {"name": "IPv4", "type": "fixed", "size": 4}]}]} - ', true, - '{"type":"record","name":"ipAddr","fields":[{"name":"addr","type":[{"type":"fixed","name":"IPv6","size":16},{"type":"fixed","name":"IPv4","size":4}]}]}'); - $record_examples [] = new SchemaExample(' + ', + true, + '{"type":"record","name":"ipAddr","fields":[{"name":"addr","type":[{"type":"fixed","name":"IPv6","size":16},{"type":"fixed","name":"IPv4","size":4}]}]}' + ); + $record_examples[] = new SchemaExample(' {"type": "record", "name": "Address", "fields": [{"type": "string"}, {"type": "string", "name": "City"}]} ', false); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "name": "Event", "fields": [{"name": "Sponsor"}, {"name": "City", "type": "string"}]} ', false); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"type": "record", "fields": "His vision, from the constantly passing bars," "name", "Rainer"} ', false); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample(' {"name": ["Tom", "Jerry"], "type": "record", "fields": [{"name": "name", "type": "string"}]} ', false); - $record_examples [] = new SchemaExample(' + $record_examples[] = new SchemaExample( + ' {"type":"record","name":"foo","doc":"doc string", "fields":[{"name":"bar", "type":"int", "order":"ascending", "default":1}]} ', true, - '{"type":"record","name":"foo","doc":"doc string","fields":[{"name":"bar","type":"int","default":1,"order":"ascending"}]}'); - $record_examples [] = new SchemaExample(' + '{"type":"record","name":"foo","doc":"doc string","fields":[{"name":"bar","type":"int","default":1,"order":"ascending"}]}' + ); + $record_examples[] = new SchemaExample(' {"type":"record", "name":"foo", "doc":"doc string", "fields":[{"name":"bar", "type":"int", "order":"bad"}]} ', false); $record_examples[] = new SchemaExample( - '{"type":"record", "name":"Record2", "aliases":["Record1"]}', false); + '{"type":"record", "name":"Record2", "aliases":["Record1"]}', + false + ); - self::$examples = array_merge($primitive_examples, + self::$examples = array_merge( + $primitive_examples, $fixed_examples, $enum_examples, $array_examples, $map_examples, $union_examples, - $record_examples); - self::$valid_examples = array(); + $record_examples + ); + self::$valid_examples = []; foreach (self::$examples as $example) { if ($example->is_valid) { - self::$valid_examples [] = $example; + self::$valid_examples[] = $example; } } } @@ -450,299 +787,19 @@ protected static function make_primitive_examples() { $examples = []; foreach ([ - 'null', - 'boolean', - 'int', - 'long', - 'float', - 'double', - 'bytes', - 'string' - ] - as $type) { - $examples [] = new SchemaExample(sprintf('"%s"', $type), true); - $examples [] = new SchemaExample(sprintf('{"type": "%s"}', $type), true, sprintf('"%s"', $type)); - } - return $examples; - } - - /** - * @dataProvider schema_examples_provider - */ - function test_parse($example): void - { - $schema_string = $example->schema_string; - try { - $normalized_schema_string = $example->normalized_schema_string; - $schema = AvroSchema::parse($schema_string); - $this->assertTrue($example->is_valid, - sprintf("schema_string: %s\n", - $schema_string)); - $this->assertEquals($normalized_schema_string, (string) $schema); - } catch (AvroSchemaParseException $e) { - $this->assertFalse($example->is_valid, - sprintf("schema_string: %s\n%s", - $schema_string, - $e->getMessage())); - } - } - - public function testToAvroIncludesAliases(): void - { - $hash = <<assertEquals($schema->toAvro(), json_decode($hash, true)); - } - - public function testValidateFieldAliases() - { - $this->expectException(AvroSchemaParseException::class); - $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.'); - AvroSchema::parse(<<expectException(AvroSchemaParseException::class); - $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.'); - AvroSchema::parse(<<expectException(AvroSchemaParseException::class); - $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.'); - AvroSchema::parse(<<expectException(AvroSchemaParseException::class); - $this->expectExceptionMessage('Invalid aliases value. Must be an array of strings.'); - AvroSchema::parse(<<expectException(AvroSchemaParseException::class); - $this->expectExceptionMessage('Alias already in use'); - AvroSchema::parse(<<expectNotToPerformAssertions(); - AvroSchema::parse(<<toAvro(), json_decode($avro, true)); - } - - public static function invalidDecimalLogicalTypeDataProvider(): array - { - return [ - 'bytes - invalid precision' => [ - '{"type": "bytes", "logicalType": "decimal", "precision": -1, "scale": 2}', - new AvroException("Precision '-1' is invalid. It must be a positive integer."), - ], - 'bytes - invalid value for precision (float)' => [ - '{"type": "bytes", "logicalType": "decimal", "precision": 11.23, "scale": 2}', - new AvroException("Invalid value '11.23' for 'precision' attribute of decimal logical type."), - ], - 'bytes - invalid value for precision (string)' => [ - '{"type": "bytes", "logicalType": "decimal", "precision": "banana", "scale": 2}', - new AvroException("Invalid value 'banana' for 'precision' attribute of decimal logical type."), - ], - 'bytes - invalid scale' => [ - '{"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": -1}', - new AvroException("Scale '-1' is invalid. It must be a non-negative integer."), - ], - 'bytes - invalid scale for precision' => [ - '{"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": 2}', - new AvroException("Scale must be a lower than precision (scale='2', precision='2')."), - ], - 'bytes - invalid value for scale (float)' => [ - '{"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": 9.12}', - new AvroException("Invalid value '9.12' for 'scale' attribute of decimal logical type."), - ], - 'bytes - invalid value for scale (string)' => [ - '{"type": "bytes", "logicalType": "decimal", "precision": 2, "scale": "two"}', - new AvroException("Invalid value 'two' for 'scale' attribute of decimal logical type."), - ], - 'fixed - invalid precision' => [ - '{"name": "fixed_decimal", "type": "fixed", "logicalType": "decimal", "size": 2, "precision": -1, "scale": 2}', - new AvroException("Precision '-1' is invalid. It must be a positive integer."), - ], - 'fixed - invalid value for precision with specified size' => [ - '{"name": "fixed_decimal", "type": "fixed", "logicalType": "decimal", "size": 2, "precision": 6, "scale": 2}', - new AvroException("Invalid precision for specified fixed size (size='2', precision='6')."), - ], - ]; - } - /** - * @dataProvider invalidDecimalLogicalTypeDataProvider - */ - public function testDecimalLogicalTypeWithInvalidParameters(string $schema, AvroException $exception): void - { - $this->expectException(get_class($exception)); - $this->expectExceptionMessage($exception->getMessage()); - AvroSchema::parse($schema); + return $examples; } } diff --git a/lang/php/test/StringIOTest.php b/lang/php/test/StringIOTest.php index 096ff30c131..fcfa44104cc 100644 --- a/lang/php/test/StringIOTest.php +++ b/lang/php/test/StringIOTest.php @@ -1,4 +1,5 @@ assertEquals(0, $strio->tell()); @@ -41,7 +42,7 @@ public function test_write() $this->assertEquals($strlen, $strio->tell()); } - public function test_seek() + public function test_seek(): void { $strio = new AvroStringIO('abcdefghijklmnopqrstuvwxyz'); $strio->seek(4, AvroIO::SEEK_SET); @@ -52,7 +53,7 @@ public function test_seek() $this->assertEquals('wxyz', $strio->read(4)); } - public function test_tell() + public function test_tell(): void { $strio = new AvroStringIO('foobar'); $this->assertEquals(0, $strio->tell()); @@ -61,7 +62,7 @@ public function test_tell() $this->assertEquals($strlen, $strio->tell()); } - public function test_read() + public function test_read(): void { $str = 'foobar'; $strio = new AvroStringIO($str); @@ -69,7 +70,7 @@ public function test_read() $this->assertEquals(substr($str, 0, $strlen), $strio->read($strlen)); } - public function test_string_rep() + public function test_string_rep(): void { $writers_schema_json = '"null"'; $writers_schema = AvroSchema::parse($writers_schema_json); @@ -79,8 +80,11 @@ public function test_string_rep() $dw = new AvroDataIOWriter($strio, $datum_writer, $writers_schema_json); $dw->close(); - $this->assertEquals(57, strlen($strio->string()), - AvroDebug::asciiString($strio->string())); + $this->assertEquals( + 57, + strlen((string) $strio->string()), + AvroDebug::asciiString($strio->string()) + ); $read_strio = new AvroStringIO($strio->string()); diff --git a/lang/php/test/generate_interop_data.php b/lang/php/test/generate_interop_data.php index fc9da0575bb..503489de2b6 100644 --- a/lang/php/test/generate_interop_data.php +++ b/lang/php/test/generate_interop_data.php @@ -1,5 +1,6 @@ #!/usr/bin/env php [5.0, -6.0, -10.5], 'mapField' => [ 'a' => ['label' => 'a'], - 'c' => ['label' => '3P0'] + 'c' => ['label' => '3P0'], ], 'unionField' => 14.5, 'enumField' => 'C', @@ -44,15 +45,15 @@ 'children' => [ [ 'label' => 'inner', - 'children' => [] - ] - ] - ] + 'children' => [], + ], + ], + ], ]; $schema_json = file_get_contents(AVRO_INTEROP_SCHEMA); foreach (AvroDataIO::validCodecs() as $codec) { - $file_name = $codec == AvroDataIO::NULL_CODEC ? 'php.avro' : sprintf('php_%s.avro', $codec); + $file_name = AvroDataIO::NULL_CODEC == $codec ? 'php.avro' : sprintf('php_%s.avro', $codec); $data_file = implode(DIRECTORY_SEPARATOR, [AVRO_BUILD_DATA_DIR, $file_name]); $io_writer = AvroDataIO::openFile($data_file, 'w', $schema_json, $codec); $io_writer->append($datum); diff --git a/lang/php/test/test_helper.php b/lang/php/test/test_helper.php index 78af6775aca..0f938a16763 100644 --- a/lang/php/test/test_helper.php +++ b/lang/php/test/test_helper.php @@ -1,4 +1,5 @@