diff --git a/.gitattributes b/.gitattributes index 0dda34b..9c56fa5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,7 @@ examples export-ignore tests export-ignore .gitattributes export-ignore .gitignore export-ignore -.php_cs.dist export-ignore +.php-cs-fixer.dist.php export-ignore dockerfile.sh export-ignore phpunit.xml.dist export-ignore psalm.xml export-ignore diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index ac312ff..8eb7ec2 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -11,24 +11,17 @@ jobs: matrix: operating-system: [ubuntu-latest] env: - - PHP_IMAGE: php:7.3-cli - - - PHP_IMAGE: php:7.4-cli - COVERAGE_FILE: coverage.clover - - - PHP_IMAGE: php:8.0-cli - QA: 1 - - - PHP_IMAGE: php:8.1-cli - PHP_IMAGE: php:8.2-cli + COVERAGE_FILE: coverage.clover - PHP_IMAGE: php:8.3-cli + QA: 1 - PHP_IMAGE: php:8.4-cli - PHP_IMAGE: php:8.5-cli runs-on: ${{ matrix.operating-system }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v6 - name: Build docker image env: ${{ matrix.env }} diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 66% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index 23f7f25..174748f 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -3,22 +3,35 @@ namespace MessagePack; use PhpCsFixer\Config; +use PhpCsFixer\Fixer\ConfigurableFixerInterface; use PhpCsFixer\Fixer\ConstantNotation\NativeConstantInvocationFixer; -use PhpCsFixer\Fixer\FixerInterface; use PhpCsFixer\Fixer\FunctionNotation\NativeFunctionInvocationFixer; +use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver; +use PhpCsFixer\FixerDefinition\FixerDefinition; +use PhpCsFixer\FixerDefinition\FixerDefinitionInterface; use PhpCsFixer\Tokenizer\Tokens; -final class FilterableFixer implements FixerInterface +final class FilterableFixer implements ConfigurableFixerInterface { - private $fixer; - private $pathRegex; + private ConfigurableFixerInterface $fixer; + private string $pathRegex; - public function __construct(FixerInterface $fixer, string $pathRegex) + public function __construct(ConfigurableFixerInterface $fixer, string $pathRegex) { $this->fixer = $fixer; $this->pathRegex = $pathRegex; } + public function configure(array $configuration): void + { + $this->fixer->configure($configuration); + } + + public function getConfigurationDefinition(): FixerConfigurationResolver + { + return $this->fixer->getConfigurationDefinition(); + } + public function isCandidate(Tokens $tokens) : bool { return $this->fixer->isCandidate($tokens); @@ -52,7 +65,12 @@ public function supports(\SplFileInfo $file) : bool return $this->fixer->supports($file); } -}; + + public function getDefinition() : FixerDefinitionInterface + { + return $this->fixer->getDefinition(); + } +} $header = <<setUsingCache(false) ->setRiskyAllowed(true) ->registerCustomFixers([ @@ -75,13 +93,17 @@ public function supports(\SplFileInfo $file) : bool '@Symfony:risky' => true, 'array_syntax' => ['syntax' => 'short'], 'binary_operator_spaces' => ['operators' => ['=' => null, '=>' => null]], - 'is_null' => false, // https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/4015 + 'fully_qualified_strict_types' => false, + 'integer_literal_case' => false, + 'is_null' => false, 'native_constant_invocation' => false, 'native_function_invocation' => false, - 'FilterableFixer/native_constant_invocation' => true, - 'FilterableFixer/native_function_invocation' => true, + 'FilterableFixer/native_constant_invocation' => ['strict' => false], + 'FilterableFixer/native_function_invocation' => ['strict' => false], 'no_useless_else' => true, 'no_useless_return' => true, + 'no_useless_concat_operator' => false, + 'no_superfluous_phpdoc_tags' => false, 'ordered_imports' => [ 'sort_algorithm' => 'alpha', 'imports_order' => ['class', 'function', 'const'], @@ -90,6 +112,7 @@ public function supports(\SplFileInfo $file) : bool 'phpdoc_order' => true, 'phpdoc_to_comment' => false, 'return_type_declaration' => ['space_before' => 'one'], + 'statement_indentation' => false, 'strict_comparison' => true, 'header_comment' => [ 'comment_type' => 'PHPDoc', diff --git a/LICENSE b/LICENSE index a9b2979..fd82d4b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2025 Eugene Leonovich +Copyright (c) 2015-2026 Eugene Leonovich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e73497e..2a6da85 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ composer require rybakit/msgpack ### Packing -To pack values you can either use an instance of a `Packer`: +To pack values, you can either use an instance of a `Packer`: ```php $packer = new Packer(); @@ -66,8 +66,8 @@ $packed = MessagePack::pack($value); In the examples above, the method `pack` automatically packs a value depending on its type. However, not all PHP types can be uniquely translated to MessagePack types. For example, the MessagePack format defines `map` and `array` types, -which are represented by a single `array` type in PHP. By default, the packer will pack a PHP array as a MessagePack -array if it has sequential numeric keys, starting from `0` and as a MessagePack map otherwise: +which are represented by a single `array` type in PHP. By default, the packer will pack a PHP array as a MessagePack +array if it has sequential numeric keys starting from `0`, and as a MessagePack map otherwise: ```php $mpArr1 = $packer->pack([1, 2]); // MP array [1, 2] @@ -144,7 +144,7 @@ $packer = new Packer(PackOptions::FORCE_FLOAT32 | PackOptions::FORCE_FLOAT64); ### Unpacking -To unpack data you can either use an instance of a `BufferUnpacker`: +To unpack data, you can either use an instance of a `BufferUnpacker`: ```php $unpacker = new BufferUnpacker(); @@ -159,8 +159,8 @@ or call a static method on the `MessagePack` class: $value = MessagePack::unpack($packed); ``` -If the packed data is received in chunks (e.g. when reading from a stream), use the `tryUnpack` method, which attempts -to unpack data and returns an array of unpacked messages (if any) instead of throwing an `InsufficientDataException`: +If the packed data is received in chunks (e.g. when reading from a stream), use the `tryUnpack` method, which attempts +to unpack the data and returns an array of unpacked messages (if any) instead of throwing an `InsufficientDataException`: ```php while ($chunk = ...) { @@ -286,8 +286,8 @@ $packedArray = $packer->pack([1, 2, 3]); As with type objects, type transformers are only responsible for *serializing* values. They should be used when you need to serialize a value that does not implement the [CanBePacked](src/CanBePacked.php) -interface. Examples of such values could be instances of built-in or third-party classes that you don't -own, or non-objects such as resources. +interface. Examples of such values include instances of built-in or third-party classes that you do not +own, or non-objects such as resources. A transformer class must implement the [CanPack](src/CanPack.php) interface. To use a transformer, it must first be registered in the packer. Here is an example of how to serialize PHP streams into @@ -346,7 +346,7 @@ $timestamp = MessagePack::unpack($packedTimestamp); In addition, the format can be extended with your own types. For example, to make the built-in PHP `DateTime` objects first-class citizens in your code, you can create a corresponding extension, as shown in the [example](examples/MessagePack/DateTimeExtension.php). -Please note, that custom extensions have to be registered with a unique extension ID (an integer from `0` to `127`). +Please note that custom extensions must be registered with a unique extension ID (an integer from `0` to `127`). > *More extension examples can be found in the [examples/MessagePack](examples/MessagePack) directory.* @@ -371,7 +371,7 @@ Run tests as follows: vendor/bin/phpunit ``` -Also, if you already have Docker installed, you can run the tests in a docker container. First, create a container: +Also, if you already have Docker installed, you can run the tests in a Docker container. First, create a container: ```sh ./dockerfile.sh | docker build -t msgpack - @@ -396,7 +396,7 @@ docker run --rm -v $PWD:/msgpack -w /msgpack msgpack #### Fuzzing To ensure that the unpacking works correctly with malformed/semi-malformed data, you can use a testing technique -called [Fuzzing](https://en.wikipedia.org/wiki/Fuzzing). The library ships with a help file (target) +called [fuzzing](https://en.wikipedia.org/wiki/Fuzzing). The library ships with a helper file (target) for [PHP-Fuzzer](https://github.com/nikic/PHP-Fuzzer) and can be used as follows: ```sh diff --git a/composer.json b/composer.json index 0531a72..4da2b6b 100644 --- a/composer.json +++ b/composer.json @@ -11,13 +11,14 @@ } ], "require": { - "php": "^7.1.1|^8" + "php": "^8.2" }, "require-dev": { + "ext-decimal": "*", "ext-gmp": "*", - "friendsofphp/php-cs-fixer": "^2.14", - "phpunit/phpunit": "^7.1|^8|^9", - "vimeo/psalm": "^3.9|^4" + "friendsofphp/php-cs-fixer": "^3", + "phpunit/phpunit": "^11", + "vimeo/psalm": "^5 || ^6" }, "suggest": { "ext-decimal": "For converting overflowed integers to Decimal objects", diff --git a/examples/MessagePack/DateTimeExtension.php b/examples/MessagePack/DateTimeExtension.php index d185ee8..0590a1e 100644 --- a/examples/MessagePack/DateTimeExtension.php +++ b/examples/MessagePack/DateTimeExtension.php @@ -15,20 +15,23 @@ use MessagePack\Extension; use MessagePack\Packer; -class DateTimeExtension implements Extension +final class DateTimeExtension implements Extension { - private $type; - - public function __construct(int $type) - { - $this->type = $type; + public function __construct( + private readonly int $type, + ) { } + #[\Override] public function getType() : int { return $this->type; } + /** + * @param mixed $value + */ + #[\Override] public function pack(Packer $packer, $value) : ?string { if (!$value instanceof \DateTimeInterface) { @@ -38,6 +41,10 @@ public function pack(Packer $packer, $value) : ?string return $packer->packExt($this->type, $value->format('YmdHisue')); } + /** + * @return \DateTimeImmutable|false + */ + #[\Override] public function unpackExt(BufferUnpacker $unpacker, int $extLength) { return \DateTimeImmutable::createFromFormat('YmdHisue', $unpacker->read($extLength)); diff --git a/examples/MessagePack/StructList.php b/examples/MessagePack/StructList.php index 9fb0058..f52c5e9 100644 --- a/examples/MessagePack/StructList.php +++ b/examples/MessagePack/StructList.php @@ -13,11 +13,8 @@ final class StructList { - /** @readonly */ - public $list; - - public function __construct(array $list) - { - $this->list = $list; + public function __construct( + public array $list, + ) { } } diff --git a/examples/MessagePack/StructListExtension.php b/examples/MessagePack/StructListExtension.php index 5dbfee9..928966b 100644 --- a/examples/MessagePack/StructListExtension.php +++ b/examples/MessagePack/StructListExtension.php @@ -15,20 +15,23 @@ use MessagePack\Extension; use MessagePack\Packer; -class StructListExtension implements Extension +final class StructListExtension implements Extension { - private $type; - - public function __construct(int $type) - { - $this->type = $type; + public function __construct( + private readonly int $type, + ) { } + #[\Override] public function getType() : int { return $this->type; } + /** + * @param mixed $value + */ + #[\Override] public function pack(Packer $packer, $value) : ?string { if (!$value instanceof StructList) { @@ -40,7 +43,12 @@ public function pack(Packer $packer, $value) : ?string return $packer->packArray($value->list); } - $keys = \array_keys(\reset($value->list)); + $first = \reset($value->list); + if (false === $first) { + return $packer->packArray($value->list); + } + + $keys = \array_keys($first); $values = ''; foreach ($value->list as $item) { @@ -56,6 +64,10 @@ public function pack(Packer $packer, $value) : ?string ); } + /** + * @return array> + */ + #[\Override] public function unpackExt(BufferUnpacker $unpacker, int $extLength) { $keys = $unpacker->unpackArray(); diff --git a/examples/MessagePack/Text.php b/examples/MessagePack/Text.php index 3870d9b..8c06e21 100644 --- a/examples/MessagePack/Text.php +++ b/examples/MessagePack/Text.php @@ -13,15 +13,12 @@ final class Text { - /** @readonly */ - public $str; - - public function __construct(string $str) - { - $this->str = $str; + public function __construct( + public readonly string $str, + ) { } - public function __toString() + public function __toString() : string { return $this->str; } diff --git a/examples/MessagePack/TextExtension.php b/examples/MessagePack/TextExtension.php index ea3f4db..4562b48 100644 --- a/examples/MessagePack/TextExtension.php +++ b/examples/MessagePack/TextExtension.php @@ -15,22 +15,24 @@ use MessagePack\Extension; use MessagePack\Packer; -class TextExtension implements Extension +final class TextExtension implements Extension { - private $type; - private $minLength; - - public function __construct(int $type, int $minLength = 100) - { - $this->type = $type; - $this->minLength = $minLength; + public function __construct( + private readonly int $type, + private readonly int $minLength = 100, + ) { } + #[\Override] public function getType() : int { return $this->type; } + /** + * @param mixed $value + */ + #[\Override] public function pack(Packer $packer, $value) : ?string { if (!$value instanceof Text) { @@ -43,18 +45,31 @@ public function pack(Packer $packer, $value) : ?string } $context = \deflate_init(\ZLIB_ENCODING_GZIP); + if (false === $context) { + return $packer->packStr($value->str); + } + $compressed = \deflate_add($context, $value->str, \ZLIB_FINISH); + if (false === $compressed) { + return $packer->packStr($value->str); + } return isset($compressed[$length - 1]) ? $packer->packStr($value->str) : $packer->packExt($this->type, $compressed); } + /** + * @return string|false + */ + #[\Override] public function unpackExt(BufferUnpacker $unpacker, int $extLength) { $compressed = $unpacker->read($extLength); $context = \inflate_init(\ZLIB_ENCODING_GZIP); - return \inflate_add($context, $compressed, \ZLIB_FINISH); + return false === $context + ? false + : \inflate_add($context, $compressed, \ZLIB_FINISH); } } diff --git a/examples/MessagePack/TraversableExtension.php b/examples/MessagePack/TraversableExtension.php index 2b0fdb0..b127a8a 100644 --- a/examples/MessagePack/TraversableExtension.php +++ b/examples/MessagePack/TraversableExtension.php @@ -15,20 +15,23 @@ use MessagePack\Extension; use MessagePack\Packer; -class TraversableExtension implements Extension +final class TraversableExtension implements Extension { - private $type; - - public function __construct(int $type) - { - $this->type = $type; + public function __construct( + private readonly int $type, + ) { } + #[\Override] public function getType() : int { return $this->type; } + /** + * @param mixed $value + */ + #[\Override] public function pack(Packer $packer, $value) : ?string { if (!$value instanceof \Traversable) { @@ -47,6 +50,10 @@ public function pack(Packer $packer, $value) : ?string ); } + /** + * @return \Generator + */ + #[\Override] public function unpackExt(BufferUnpacker $unpacker, int $extLength) { $size = $unpacker->unpackArrayHeader(); diff --git a/examples/MessagePack/Uint64.php b/examples/MessagePack/Uint64.php index 1e31d06..348dcae 100644 --- a/examples/MessagePack/Uint64.php +++ b/examples/MessagePack/Uint64.php @@ -16,12 +16,9 @@ final class Uint64 implements CanBePacked { - /** @readonly */ - public $value; - - public function __construct(string $value) - { - $this->value = $value; + public function __construct( + public readonly string $value, + ) { } public function __toString() : string @@ -29,6 +26,7 @@ public function __toString() : string return $this->value; } + #[\Override] public function pack(Packer $packer) : string { return "\xcf".\gmp_export($this->value); diff --git a/examples/binary.php b/examples/binary.php index 9e76c2a..ec626d8 100644 --- a/examples/binary.php +++ b/examples/binary.php @@ -18,8 +18,13 @@ $packer = new Packer(); $packed = $packer->pack(['name' => new Bin('value')]); +$bytes = unpack('C*', $packed); -echo '[', implode(', ', unpack('C*', $packed)), "]\n"; +if (false === $bytes) { + exit(1); +} + +echo '[', implode(', ', $bytes), "]\n"; /* OUTPUT [129, 164, 110, 97, 109, 101, 196, 5, 118, 97, 108, 117, 101] diff --git a/examples/compressed_string.php b/examples/compressed_string.php index a858422..6ee6884 100644 --- a/examples/compressed_string.php +++ b/examples/compressed_string.php @@ -42,11 +42,11 @@ printf("Packed string size: %dB\n", strlen($packedString)); printf("Packed text size: %dB\n", strlen($packedCompressedString)); printf("Space saved: %dB\n", strlen($packedString) - strlen($packedCompressedString)); -printf("Percentage saved: %d%%\n", round(1 - strlen($packedCompressedString) / strlen($packedString), 2) * 100); +printf("Percentage saved: %d%%\n", 100 - intdiv(strlen($packedCompressedString) * 100, strlen($packedString))); /* OUTPUT Packed string size: 448B Packed text size: 291B Space saved: 157B -Percentage saved: 35% +Percentage saved: 36% */ diff --git a/examples/struct_list.php b/examples/struct_list.php index f1e67fe..9bfd998 100644 --- a/examples/struct_list.php +++ b/examples/struct_list.php @@ -42,7 +42,7 @@ printf("Packed list size: %dB\n", strlen($packedList)); printf("Packed struct list size: %dB\n", strlen($packedStructList)); printf("Space saved: %dB\n", strlen($packedList) - strlen($packedStructList)); -printf("Percentage saved: %d%%\n", round(1 - strlen($packedStructList) / strlen($packedList), 2) * 100); +printf("Percentage saved: %d%%\n", 100 - intdiv(strlen($packedStructList) * 100, strlen($packedList))); /* OUTPUT Packed list size: 5287B diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f61e143..3fdfd25 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,15 +2,9 @@ @@ -29,9 +23,9 @@ - - + + src - - + + diff --git a/psalm.xml b/psalm.xml index d1cbaa6..4d0b1e6 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,11 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/BufferUnpacker.php b/src/BufferUnpacker.php index 8f71ce4..54027ee 100644 --- a/src/BufferUnpacker.php +++ b/src/BufferUnpacker.php @@ -597,6 +597,7 @@ private function unpackUint64() throw new InsufficientDataException(); } + /** @psalm-suppress PossiblyInvalidArrayAccess */ $num = \unpack('J', $this->buffer, $this->offset)[1]; $this->offset += 8; @@ -604,7 +605,7 @@ private function unpackUint64() return $num; } if ($this->isBigIntAsDec) { - return new Decimal(\sprintf('%u', $num)); + return Decimal::valueOf(\sprintf('%u', $num)); } if ($this->isBigIntAsGmp) { return \gmp_import(\substr($this->buffer, $this->offset - 8, 8)); @@ -622,6 +623,7 @@ private function unpackUint64MapKey() throw new InsufficientDataException(); } + /** @psalm-suppress PossiblyInvalidArrayAccess */ $num = \unpack('J', $this->buffer, $this->offset)[1]; $this->offset += 8; @@ -686,6 +688,7 @@ private function unpackInt64() throw new InsufficientDataException(); } + /** @psalm-suppress PossiblyInvalidArrayAccess */ $num = \unpack('J', $this->buffer, $this->offset)[1]; $this->offset += 8; @@ -701,6 +704,7 @@ private function unpackFloat32() throw new InsufficientDataException(); } + /** @psalm-suppress PossiblyInvalidArrayAccess */ $num = \unpack('G', $this->buffer, $this->offset)[1]; $this->offset += 4; @@ -716,6 +720,7 @@ private function unpackFloat64() throw new InsufficientDataException(); } + /** @psalm-suppress PossiblyInvalidArrayAccess */ $num = \unpack('E', $this->buffer, $this->offset)[1]; $this->offset += 8; diff --git a/src/Exception/PackingFailedException.php b/src/Exception/PackingFailedException.php index f2e6fdd..deb289a 100644 --- a/src/Exception/PackingFailedException.php +++ b/src/Exception/PackingFailedException.php @@ -19,7 +19,7 @@ class PackingFailedException extends \RuntimeException public static function unsupportedType($value) : self { return new self(\sprintf('Unsupported type "%s", maybe you forgot to register the type transformer or extension?', - \is_object($value) ? \get_class($value) : \gettype($value) + \is_object($value) ? $value::class : \gettype($value) )); } } diff --git a/src/Extension/TimestampExtension.php b/src/Extension/TimestampExtension.php index a35ede4..3415815 100644 --- a/src/Extension/TimestampExtension.php +++ b/src/Extension/TimestampExtension.php @@ -20,11 +20,16 @@ final class TimestampExtension implements Extension { private const TYPE = -1; + #[\Override] public function getType() : int { return self::TYPE; } + /** + * @param mixed $value + */ + #[\Override] public function pack(Packer $packer, $value) : ?string { if (!$value instanceof Timestamp) { @@ -46,6 +51,7 @@ public function pack(Packer $packer, $value) : ?string /** * @return Timestamp */ + #[\Override] public function unpackExt(BufferUnpacker $unpacker, int $extLength) { if (4 === $extLength) { @@ -61,6 +67,7 @@ public function unpackExt(BufferUnpacker $unpacker, int $extLength) if (8 === $extLength) { $data = $unpacker->read(8); + /** @psalm-suppress PossiblyInvalidArrayAccess */ $num = \unpack('J', $data)[1]; $nsec = $num >> 34; if ($nsec < 0) { @@ -77,6 +84,7 @@ public function unpackExt(BufferUnpacker $unpacker, int $extLength) | \ord($data[2]) << 8 | \ord($data[3]); + /** @psalm-suppress PossiblyInvalidArrayAccess */ return new Timestamp(\unpack('J', $data, 4)[1], $nsec); } } diff --git a/src/MessagePack.php b/src/MessagePack.php index ca679c5..30339a9 100644 --- a/src/MessagePack.php +++ b/src/MessagePack.php @@ -23,6 +23,8 @@ final class MessagePack /** * @codeCoverageIgnore + * + * @psalm-suppress UnusedConstructor */ private function __construct() { diff --git a/src/Packer.php b/src/Packer.php index a4150fc..33f6941 100644 --- a/src/Packer.php +++ b/src/Packer.php @@ -110,11 +110,7 @@ public function pack($value) return $this->isDetectArrMap || $this->isForceArr ? "\x90" : "\x80"; } if ($this->isDetectArrMap) { - if (!isset($value[0]) && !\array_key_exists(0, $value)) { - return $this->packMap($value); - } - - return \array_values($value) === $value + return \array_is_list($value) ? $this->packArray($value) : $this->packMap($value); } diff --git a/src/Type/Bin.php b/src/Type/Bin.php index 1d76b14..ed3affd 100644 --- a/src/Type/Bin.php +++ b/src/Type/Bin.php @@ -24,6 +24,7 @@ public function __construct(string $data) $this->data = $data; } + #[\Override] public function pack(Packer $packer) : string { return $packer->packBin($this->data); diff --git a/src/Type/Ext.php b/src/Type/Ext.php index f59761a..484349e 100644 --- a/src/Type/Ext.php +++ b/src/Type/Ext.php @@ -28,6 +28,7 @@ public function __construct(int $type, string $data) $this->data = $data; } + #[\Override] public function pack(Packer $packer) : string { return $packer->packExt($this->type, $this->data); diff --git a/src/Type/Map.php b/src/Type/Map.php index 68891b5..867a01c 100644 --- a/src/Type/Map.php +++ b/src/Type/Map.php @@ -24,6 +24,7 @@ public function __construct(array $map) $this->map = $map; } + #[\Override] public function pack(Packer $packer) : string { return $packer->packMap($this->map); diff --git a/src/TypeTransformer/StreamTransformer.php b/src/TypeTransformer/StreamTransformer.php index fc32d82..ad7cc39 100644 --- a/src/TypeTransformer/StreamTransformer.php +++ b/src/TypeTransformer/StreamTransformer.php @@ -16,10 +16,15 @@ class StreamTransformer implements CanPack { + #[\Override] public function pack(Packer $packer, $value) : ?string { - return \is_resource($value) && 'stream' === \get_resource_type($value) - ? $packer->packBin(\stream_get_contents($value)) - : null; + if (!\is_resource($value) || 'stream' !== \get_resource_type($value)) { + return null; + } + + $contents = \stream_get_contents($value); + + return false === $contents ? null : $packer->packBin($contents); } } diff --git a/src/TypeTransformer/TraversableTransformer.php b/src/TypeTransformer/TraversableTransformer.php index 799642e..efdb6c0 100644 --- a/src/TypeTransformer/TraversableTransformer.php +++ b/src/TypeTransformer/TraversableTransformer.php @@ -16,15 +16,9 @@ class TraversableTransformer implements CanPack { - /** @var bool */ - private $packToMap; - - /** - * @param bool $packToMap - */ - private function __construct($packToMap) - { - $this->packToMap = $packToMap; + private function __construct( + private readonly bool $packToMap, + ) { } public static function toMap() : self @@ -37,6 +31,10 @@ public static function toArray() : self return new self(false); } + /** + * @param mixed $value + */ + #[\Override] public function pack(Packer $packer, $value) : ?string { if (!$value instanceof \Traversable) { diff --git a/tests/DataProvider.php b/tests/DataProvider.php index 46433cb..b858b3a 100644 --- a/tests/DataProvider.php +++ b/tests/DataProvider.php @@ -247,9 +247,9 @@ public static function provideMapData() : array 'fix map #2' => [['abc' => 5], "\x81\xa3\x61\x62\x63\x05"], 'fix map #3' => [["\x80" => 0xffff], "\x81\xc4\x01\x80\xcd\xff\xff"], 'fix map #4' => [[-1 => -1, 1 => 1], "\x82\xff\xff\x01\x01"], - '16-bit map #1' => [array_fill(1, 16, 0x05), "\xde\x00\x10".array_reduce(range(1, 16), function ($r, $i) { return $r .= pack('C', $i)."\x05"; })], - '16-bit map #2' => [array_fill(1, 65535, 0x05), "\xde\xff\xff".array_reduce(range(1, 127), function ($r, $i) { return $r .= pack('C', $i)."\x05"; }).array_reduce(range(128, 255), function ($r, $i) { return $r .= "\xcc".pack('C', $i)."\x05"; }).array_reduce(range(256, 65535), function ($r, $i) { return $r .= "\xcd".pack('n', $i)."\x05"; })], - '32-bit map' => [array_fill(1, 65536, 0x05), "\xdf\x00\x01\x00\x00".array_reduce(range(1, 127), function ($r, $i) { return $r .= pack('C', $i)."\x05"; }).array_reduce(range(128, 255), function ($r, $i) { return $r .= "\xcc".pack('C', $i)."\x05"; }).array_reduce(range(256, 65535), function ($r, $i) { return $r .= "\xcd".pack('n', $i)."\x05"; })."\xce".pack('N', 65536)."\x05"], + '16-bit map #1' => [array_fill(1, 16, 0x05), "\xde\x00\x10".array_reduce(range(1, 16), static function ($r, $i) { return $r .= pack('C', $i)."\x05"; })], + '16-bit map #2' => [array_fill(1, 65535, 0x05), "\xde\xff\xff".array_reduce(range(1, 127), static function ($r, $i) { return $r .= pack('C', $i)."\x05"; }).array_reduce(range(128, 255), static function ($r, $i) { return $r .= "\xcc".pack('C', $i)."\x05"; }).array_reduce(range(256, 65535), static function ($r, $i) { return $r .= "\xcd".pack('n', $i)."\x05"; })], + '32-bit map' => [array_fill(1, 65536, 0x05), "\xdf\x00\x01\x00\x00".array_reduce(range(1, 127), static function ($r, $i) { return $r .= pack('C', $i)."\x05"; }).array_reduce(range(128, 255), static function ($r, $i) { return $r .= "\xcc".pack('C', $i)."\x05"; }).array_reduce(range(256, 65535), static function ($r, $i) { return $r .= "\xcd".pack('n', $i)."\x05"; })."\xce".pack('N', 65536)."\x05"], 'complex map' => [[1 => [[1 => 2, 3 => 4], [1 => null]], 2 => 1, 3 => [false, 'def'], 4 => [0x100000000 => 'a', 0xffffffff => 'b']], "\x84\x01\x92\x82\x01\x02\x03\x04\x81\x01\xc0\x02\x01\x03\x92\xc2\xa3\x64\x65\x66\x04\x82\xcf\x00\x00\x00\x01\x00\x00\x00\x00\xa1\x61\xce\xff\xff\xff\xff\xa1\x62"], ]; } diff --git a/tests/ExamplesTest.php b/tests/ExamplesTest.php index 3d0ff32..e48f230 100644 --- a/tests/ExamplesTest.php +++ b/tests/ExamplesTest.php @@ -29,7 +29,7 @@ public function testExample(string $filename) : void } } - public function provideExampleData() : iterable + public static function provideExampleData() : iterable { $dir = dirname(__DIR__).'/examples'; foreach (glob("$dir/*.php") as $filename) { diff --git a/tests/Perf/Benchmark/DurationBenchmark.php b/tests/Perf/Benchmark/DurationBenchmark.php index 1560def..57a6de7 100644 --- a/tests/Perf/Benchmark/DurationBenchmark.php +++ b/tests/Perf/Benchmark/DurationBenchmark.php @@ -23,9 +23,6 @@ public function __construct($duration) $this->duration = $duration; } - /** - * {@inheritdoc} - */ public function benchmark(Target $target, Test $test) { $target->sanitize($test); diff --git a/tests/Perf/Runner.php b/tests/Perf/Runner.php index 9c7fd22..b6ccb84 100644 --- a/tests/Perf/Runner.php +++ b/tests/Perf/Runner.php @@ -20,7 +20,7 @@ class Runner private $testData; private $writer; - public function __construct(iterable $testData, Writer $writer = null) + public function __construct(iterable $testData, ?Writer $writer = null) { $this->testData = $testData; $this->writer = $writer ?: new TableWriter(); diff --git a/tests/Perf/Target/BufferUnpackerTarget.php b/tests/Perf/Target/BufferUnpackerTarget.php index 0b9c490..e40a36f 100644 --- a/tests/Perf/Target/BufferUnpackerTarget.php +++ b/tests/Perf/Target/BufferUnpackerTarget.php @@ -20,7 +20,7 @@ class BufferUnpackerTarget implements Target private $name; private $bufferUnpacker; - public function __construct(string $name = null, BufferUnpacker $bufferUnpacker = null) + public function __construct(?string $name = null, ?BufferUnpacker $bufferUnpacker = null) { $this->bufferUnpacker = $bufferUnpacker ?: new BufferUnpacker('', null, [new TimestampExtension()]); $this->name = $name ?: get_class($this->bufferUnpacker); diff --git a/tests/Perf/Target/PackerTarget.php b/tests/Perf/Target/PackerTarget.php index 39f73c6..a3481da 100644 --- a/tests/Perf/Target/PackerTarget.php +++ b/tests/Perf/Target/PackerTarget.php @@ -20,7 +20,7 @@ class PackerTarget implements Target private $name; private $packer; - public function __construct(string $name = null, Packer $packer = null) + public function __construct(?string $name = null, ?Packer $packer = null) { $this->packer = $packer ?: new Packer(null, [new TimestampExtension()]); $this->name = $name ?: get_class($this->packer); diff --git a/tests/Perf/TestSkippedException.php b/tests/Perf/TestSkippedException.php index cc6c344..6ba7ee5 100644 --- a/tests/Perf/TestSkippedException.php +++ b/tests/Perf/TestSkippedException.php @@ -16,7 +16,7 @@ class TestSkippedException extends \RuntimeException /** @var Test */ private $test; - public function __construct(Test $test, int $code = 0, \Throwable $previous = null) + public function __construct(Test $test, int $code = 0, ?\Throwable $previous = null) { $message = sprintf('"%s" test is skipped', $test->getName()); diff --git a/tests/Perf/Writer/TableWriter.php b/tests/Perf/Writer/TableWriter.php index f8dd46c..1d817ad 100644 --- a/tests/Perf/Writer/TableWriter.php +++ b/tests/Perf/Writer/TableWriter.php @@ -32,7 +32,7 @@ class TableWriter implements Writer private $failed = []; private $ignored = []; - public function __construct(bool $ignoreIncomplete = null) + public function __construct(?bool $ignoreIncomplete = null) { $this->ignoreIncomplete = $ignoreIncomplete ?? true; } @@ -109,7 +109,7 @@ public function close() : void { echo str_repeat('=', $this->width)."\n"; - $this->writeSummary('Total', $this->total, function ($value) { + $this->writeSummary('Total', $this->total, static function ($value) { return sprintf('%.4f', $value); }); @@ -118,7 +118,7 @@ public function close() : void $this->writeSummary('Ignored', $this->ignored); } - private function writeSummary(string $title, array $values, \Closure $formatter = null) : void + private function writeSummary(string $title, array $values, ?\Closure $formatter = null) : void { $cells = [$title]; diff --git a/tests/Unit/BufferUnpackerTest.php b/tests/Unit/BufferUnpackerTest.php index cb7058f..0c2273c 100644 --- a/tests/Unit/BufferUnpackerTest.php +++ b/tests/Unit/BufferUnpackerTest.php @@ -25,8 +25,6 @@ final class BufferUnpackerTest extends TestCase { - use PhpUnitCompat; - /** * @var BufferUnpacker */ @@ -70,7 +68,7 @@ public function testUnpackThrowsExceptionOnInsufficientData(string $data) : void self::fail(InsufficientDataException::class.' was not thrown'); } - public function provideInsufficientData() : array + public static function provideInsufficientData() : array { return [ 'str' => [''], @@ -338,6 +336,7 @@ public function testTryUnpackTruncatesBuffer() : void /** * @dataProvider provideOptionsData + * * @doesNotPerformAssertions */ public function testConstructorSetsOptions($options) : void @@ -345,7 +344,7 @@ public function testConstructorSetsOptions($options) : void new BufferUnpacker('', $options); } - public function provideOptionsData() : iterable + public static function provideOptionsData() : iterable { return [ [0], @@ -367,7 +366,7 @@ public function testConstructorThrowsExceptionOnInvalidOptions($options) : void new BufferUnpacker('', $options); } - public function provideInvalidOptionsData() : iterable + public static function provideInvalidOptionsData() : iterable { return [ [UnpackOptions::BIGINT_AS_STR | UnpackOptions::BIGINT_AS_GMP], @@ -661,7 +660,7 @@ public function testUnpackThrowsExceptionOnInvalidMapKey(string $packedMap, stri $this->unpacker->unpack(); } - public function provideMapWithInvalidKeyData() : iterable + public static function provideMapWithInvalidKeyData() : iterable { $data = static function () { yield 'nil' => DataProvider::provideNilData(); @@ -745,7 +744,7 @@ public function testUnpackAllowsZeroLengthExtData(string $data) : void self::assertSame('', $ext->data); } - public function provideInvalidExtBodyData() : iterable + public static function provideInvalidExtBodyData() : iterable { return [ 'ext8' => ["\xc7\x00\x01"], diff --git a/tests/Unit/Exception/InvalidOptionExceptionTest.php b/tests/Unit/Exception/InvalidOptionExceptionTest.php index 011b1e2..aa545fd 100644 --- a/tests/Unit/Exception/InvalidOptionExceptionTest.php +++ b/tests/Unit/Exception/InvalidOptionExceptionTest.php @@ -26,7 +26,7 @@ public function testOutOfRange(string $invalidOption, array $validOptions, strin self::assertSame($message, $exception->getMessage()); } - public function provideOutOfRangeData() : array + public static function provideOutOfRangeData() : array { return [ ['foobar', ['foo'], 'Invalid option foobar, use foo'], diff --git a/tests/Unit/PackOptionsTest.php b/tests/Unit/PackOptionsTest.php index c09b92a..dc964cd 100644 --- a/tests/Unit/PackOptionsTest.php +++ b/tests/Unit/PackOptionsTest.php @@ -27,7 +27,7 @@ public function testFromBitmask(string $isserName, bool $expectedResult, int $bi self::assertSame($expectedResult, $options->{$isserName}()); } - public function provideIsserData() : array + public static function provideIsserData() : array { return [ ['isDetectStrBinMode', false, 0], @@ -82,7 +82,7 @@ public function testFromBitmaskWithInvalidOptions(int $bitmask, string $errorMes self::fail(InvalidOptionException::class.' was not thrown'); } - public function provideInvalidOptionsData() : iterable + public static function provideInvalidOptionsData() : iterable { yield [ PackOptions::FORCE_STR | PackOptions::FORCE_BIN, diff --git a/tests/Unit/PackerTest.php b/tests/Unit/PackerTest.php index 9223003..7510c18 100644 --- a/tests/Unit/PackerTest.php +++ b/tests/Unit/PackerTest.php @@ -22,12 +22,7 @@ final class PackerTest extends TestCase { - use PhpUnitCompat; - - /** - * @var Packer - */ - private $packer; + private Packer $packer; protected function setUp() : void { @@ -55,7 +50,7 @@ public function testPackThrowsExceptionOnUnsupportedType($value, string $type) : $this->packer->pack($value); } - public function provideUnsupportedTypeData() : array + public static function provideUnsupportedTypeData() : array { return [ [tmpfile(), 'resource'], @@ -71,7 +66,7 @@ public function testConstructorSetsOptions($options, $raw, string $packed) : voi self::assertSame($packed, (new Packer($options))->pack($raw)); } - public function provideOptionsData() : array + public static function provideOptionsData() : array { return [ [null, "\x80", "\xa1\x80"], @@ -114,7 +109,7 @@ public function testConstructorThrowsExceptionOnInvalidOptions($options) : void new Packer($options); } - public function provideInvalidOptionsData() : array + public static function provideInvalidOptionsData() : array { return [ [PackOptions::FORCE_STR | PackOptions::FORCE_BIN], diff --git a/tests/Unit/PhpUnitCompat.php b/tests/Unit/PhpUnitCompat.php deleted file mode 100644 index f99d6e5..0000000 --- a/tests/Unit/PhpUnitCompat.php +++ /dev/null @@ -1,28 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace MessagePack\Tests\Unit; - -/** - * Compatibility layer for legacy PHPUnit versions. - */ -trait PhpUnitCompat -{ - /** - * TestCase::expectExceptionMessageRegExp() is deprecated since PHPUnit 8.4. - */ - public function expectExceptionMessageMatches(string $regularExpression) : void - { - is_callable(parent::class.'::expectExceptionMessageMatches') - ? parent::expectExceptionMessageMatches(...func_get_args()) - : parent::expectExceptionMessageRegExp(...func_get_args()); - } -} diff --git a/tests/Unit/TypeTransformer/TraversableTransformerTest.php b/tests/Unit/TypeTransformer/TraversableTransformerTest.php index ed72549..4145434 100644 --- a/tests/Unit/TypeTransformer/TraversableTransformerTest.php +++ b/tests/Unit/TypeTransformer/TraversableTransformerTest.php @@ -16,7 +16,7 @@ use MessagePack\TypeTransformer\TraversableTransformer; use PHPUnit\Framework\TestCase; -class TraversableTransformerTest extends TestCase +final class TraversableTransformerTest extends TestCase { public function testPackTraversableToMap() : void { diff --git a/tests/Unit/UnpackOptionsTest.php b/tests/Unit/UnpackOptionsTest.php index 5f79da7..981f13b 100644 --- a/tests/Unit/UnpackOptionsTest.php +++ b/tests/Unit/UnpackOptionsTest.php @@ -27,7 +27,7 @@ public function testFromBitmask(string $isserName, bool $expectedResult, int $bi self::assertSame($expectedResult, $options->{$isserName}()); } - public function provideIsserData() : array + public static function provideIsserData() : array { return [ ['isBigIntAsStrMode', true, 0], @@ -63,7 +63,7 @@ public function testFromBitmaskWithInvalidOptions(int $bitmask, string $errorMes self::fail(InvalidOptionException::class.' was not thrown'); } - public function provideInvalidOptionsData() : iterable + public static function provideInvalidOptionsData() : iterable { yield [ UnpackOptions::BIGINT_AS_GMP | UnpackOptions::BIGINT_AS_STR, diff --git a/tests/bench.php b/tests/bench.php index 38d4c71..4ed5316 100644 --- a/tests/bench.php +++ b/tests/bench.php @@ -33,7 +33,7 @@ exit(42); } -function resolve_filter($testNames) +function resolve_filter(string $testNames) : ListFilter|RegexFilter { if ('/' === $testNames[0]) { return new RegexFilter($testNames); @@ -81,11 +81,11 @@ function resolve_filter($testNames) } $targetFactories = [ - 'pecl_p' => function () { return new PeclFunctionPackTarget(); }, - 'pecl_u' => function () { return new PeclFunctionUnpackTarget(); }, - 'pure_p' => function () { return new PackerTarget('Packer'); }, - 'pure_pdsb' => function () { return new PackerTarget('Packer (detect_str_bin)', new Packer(PackOptions::DETECT_STR_BIN)); }, - 'pure_u' => function () { return new BufferUnpackerTarget('BufferUnpacker'); }, + 'pecl_p' => static function () { return new PeclFunctionPackTarget(); }, + 'pecl_u' => static function () { return new PeclFunctionUnpackTarget(); }, + 'pure_p' => static function () { return new PackerTarget('Packer'); }, + 'pure_pdsb' => static function () { return new PackerTarget('Packer (detect_str_bin)', new Packer(PackOptions::DETECT_STR_BIN)); }, + 'pure_u' => static function () { return new BufferUnpackerTarget('BufferUnpacker'); }, ]; $targets = [];