diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 55d3e4e5c..3469d76ff 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -253,7 +253,7 @@ parameters: path: tests/Data/DataInterfaceTestAbstract.php - - rawMessage: 'Parameter #1 $data of class chillerlan\QRCode\Data\Byte constructor expects string, string|false given.' + rawMessage: 'Parameter #1 $data of class chillerlan\QRCode\Data\Byte constructor expects string|null, string|false given.' identifier: argument.type count: 1 path: tests/Data/ECITest.php diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index e19467b73..e3d355427 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -44,7 +44,7 @@ final class AlphaNum extends QRDataModeAbstract{ $bitBuffer ->put(self::DATAMODE, 4) - ->put($len, $this::getLengthBits($versionNumber)) + ->put($len, $this->getLengthBits($versionNumber)) ; // encode 2 characters in 11 bits @@ -68,8 +68,8 @@ final class AlphaNum extends QRDataModeAbstract{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ - $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read($this->getLengthBits($versionNumber)); $result = ''; // Read two characters at a time while($length > 1){ @@ -79,8 +79,8 @@ final class AlphaNum extends QRDataModeAbstract{ } $nextTwoCharsBits = $bitBuffer->read(11); - $result .= self::chr(intdiv($nextTwoCharsBits, 45)); - $result .= self::chr($nextTwoCharsBits % 45); + $result .= $this->chr(intdiv($nextTwoCharsBits, 45)); + $result .= $this->chr($nextTwoCharsBits % 45); $length -= 2; } @@ -90,7 +90,7 @@ final class AlphaNum extends QRDataModeAbstract{ throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore } - $result .= self::chr($bitBuffer->read(6)); + $result .= $this->chr($bitBuffer->read(6)); } return $result; @@ -113,7 +113,7 @@ final class AlphaNum extends QRDataModeAbstract{ /** * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - private static function chr(int $ord):string{ + private function chr(int $ord):string{ if($ord < 0 || $ord > 44){ throw new QRCodeDataException('invalid character code'); // @codeCoverageIgnore diff --git a/src/Data/Byte.php b/src/Data/Byte.php index 00f611d40..69f7279d1 100644 --- a/src/Data/Byte.php +++ b/src/Data/Byte.php @@ -37,7 +37,7 @@ final class Byte extends QRDataModeAbstract{ $bitBuffer ->put(self::DATAMODE, 4) - ->put($len, $this::getLengthBits($versionNumber)) + ->put($len, $this->getLengthBits($versionNumber)) ; $i = 0; @@ -55,8 +55,8 @@ final class Byte extends QRDataModeAbstract{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ - $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read($this->getLengthBits($versionNumber)); if($bitBuffer->available() < (8 * $length)){ throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore diff --git a/src/Data/ECI.php b/src/Data/ECI.php index fd51ce6c2..34f96e547 100644 --- a/src/Data/ECI.php +++ b/src/Data/ECI.php @@ -26,22 +26,26 @@ final class ECI extends QRDataModeAbstract{ public const DATAMODE = Mode::ECI; /** - * The current ECI encoding id + * The current ECI encoding id (default to UTF-8) */ - private int $encoding; + private int $encoding = ECICharset::ISO_IEC_10646_UTF_8; /** * @inheritDoc * @throws \chillerlan\QRCode\Data\QRCodeDataException * @noinspection PhpMissingParentConstructorInspection */ - public function __construct(int $encoding){ + public function __construct(int|null $encoding = null){ - if($encoding < 0 || $encoding > 999999){ - throw new QRCodeDataException(sprintf('invalid encoding id: "%s"', $encoding)); + if($encoding !== null){ + + if($encoding < 0 || $encoding > 999999){ + throw new QRCodeDataException(sprintf('invalid encoding id: "%s"', $encoding)); + } + + $this->encoding = $encoding; } - $this->encoding = $encoding; } public function getLengthInBits():int{ @@ -76,7 +80,7 @@ final class ECI extends QRDataModeAbstract{ $bitBuffer->put(($this->encoding | 0xC00000), 24); } else{ - throw new QRCodeDataException('invalid ECI ID'); + throw new QRCodeDataException('invalid ECI ID'); // @codeCoverageIgnore } return $this; @@ -87,7 +91,7 @@ final class ECI extends QRDataModeAbstract{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public static function parseValue(BitBuffer $bitBuffer):ECICharset{ + public function parseValue(BitBuffer $bitBuffer):ECICharset{ $firstByte = $bitBuffer->read(8); // just one byte @@ -121,23 +125,25 @@ final class ECI extends QRDataModeAbstract{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ - $eciCharset = self::parseValue($bitBuffer); + public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $eciCharset = $this->parseValue($bitBuffer); $nextMode = $bitBuffer->read(4); $encoding = $eciCharset->getName(); // this is definitely weird, but there are QR Codes out in the wild // that have ECI followed by numeric and alphanum segments // @see https://github.com/chillerlan/php-qrcode/discussions/289 - $data = match($nextMode){ - Mode::NUMBER => Number::decodeSegment($bitBuffer, $versionNumber), - Mode::ALPHANUM => AlphaNum::decodeSegment($bitBuffer, $versionNumber), - Mode::BYTE => Byte::decodeSegment($bitBuffer, $versionNumber), + $dataMode = match($nextMode){ + Mode::NUMBER => new Number, + Mode::ALPHANUM => new AlphaNum, + Mode::BYTE => new Byte, default => throw new QRCodeDataException( sprintf('ECI designator followed by invalid mode: "%04b"', $nextMode), ), }; + $data = $dataMode->decodeSegment($bitBuffer, $versionNumber); + if($encoding === null){ // The spec isn't clear on this mode; see // section 6.4.5: it does not say which encoding to assuming diff --git a/src/Data/Hanzi.php b/src/Data/Hanzi.php index 9e64c02e6..48d19429a 100644 --- a/src/Data/Hanzi.php +++ b/src/Data/Hanzi.php @@ -125,8 +125,8 @@ final class Hanzi extends QRDataModeAbstract{ $bitBuffer ->put(self::DATAMODE, 4) - ->put($this::GB2312_SUBSET, 4) - ->put($this->getCharCount(), $this::getLengthBits($versionNumber)) + ->put(self::GB2312_SUBSET, 4) + ->put($this->getCharCount(), $this->getLengthBits($versionNumber)) ; $len = strlen($this->data); @@ -159,14 +159,14 @@ final class Hanzi extends QRDataModeAbstract{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ // Hanzi mode contains a subset indicator right after mode indicator if($bitBuffer->read(4) !== self::GB2312_SUBSET){ throw new QRCodeDataException('ecpected subset indicator for Hanzi mode'); } - $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + $length = $bitBuffer->read($this->getLengthBits($versionNumber)); if($bitBuffer->available() < ($length * 13)){ throw new QRCodeDataException('not enough bits available'); diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php index 8c1da4b94..1e657b342 100644 --- a/src/Data/Kanji.php +++ b/src/Data/Kanji.php @@ -113,7 +113,7 @@ final class Kanji extends QRDataModeAbstract{ $bitBuffer ->put(self::DATAMODE, 4) - ->put($this->getCharCount(), $this::getLengthBits($versionNumber)) + ->put($this->getCharCount(), $this->getLengthBits($versionNumber)) ; $len = strlen($this->data); @@ -146,8 +146,8 @@ final class Kanji extends QRDataModeAbstract{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ - $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read($this->getLengthBits($versionNumber)); if($bitBuffer->available() < ($length * 13)){ throw new QRCodeDataException('not enough bits available'); // @codeCoverageIgnore diff --git a/src/Data/Number.php b/src/Data/Number.php index 388d80b10..dda98494b 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -37,7 +37,7 @@ final class Number extends QRDataModeAbstract{ $bitBuffer ->put(self::DATAMODE, 4) - ->put($len, $this::getLengthBits($versionNumber)) + ->put($len, $this->getLengthBits($versionNumber)) ; $i = 0; @@ -90,8 +90,8 @@ final class Number extends QRDataModeAbstract{ * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ - $length = $bitBuffer->read(self::getLengthBits($versionNumber)); + public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string{ + $length = $bitBuffer->read($this->getLengthBits($versionNumber)); $result = ''; // Read three digits at a time while($length >= 3){ diff --git a/src/Data/QRDataModeAbstract.php b/src/Data/QRDataModeAbstract.php index 560d631a9..cef7e3d67 100644 --- a/src/Data/QRDataModeAbstract.php +++ b/src/Data/QRDataModeAbstract.php @@ -21,21 +21,25 @@ abstract class QRDataModeAbstract implements QRDataModeInterface{ /** * The data to write */ - protected string $data; + protected string $data = ''; /** * QRDataModeAbstract constructor. * * @throws \chillerlan\QRCode\Data\QRCodeDataException */ - public function __construct(string $data){ - $data = $this::convertEncoding($data); + public function __construct(string|null $data = null){ - if(!$this::validateString($data)){ - throw new QRCodeDataException('invalid data'); + if($data !== null){ + $data = $this::convertEncoding($data); + + if(!$this::validateString($data)){ + throw new QRCodeDataException('invalid data'); + } + + $this->data = $data; } - $this->data = $data; } /** @@ -52,7 +56,7 @@ abstract class QRDataModeAbstract implements QRDataModeInterface{ /** * shortcut */ - protected static function getLengthBits(int $versionNumber):int{ + protected function getLengthBits(int $versionNumber):int{ return Mode::getLengthBitsForVersion(static::DATAMODE, $versionNumber); } diff --git a/src/Data/QRDataModeInterface.php b/src/Data/QRDataModeInterface.php index f553a90ac..0f9f38598 100644 --- a/src/Data/QRDataModeInterface.php +++ b/src/Data/QRDataModeInterface.php @@ -55,6 +55,6 @@ interface QRDataModeInterface{ /** * reads a segment from the BitBuffer and decodes in the current data mode */ - public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string; + public function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):string; } diff --git a/src/Decoder/Decoder.php b/src/Decoder/Decoder.php index 49035b400..a34e954ce 100644 --- a/src/Decoder/Decoder.php +++ b/src/Decoder/Decoder.php @@ -117,16 +117,16 @@ final class Decoder{ break; } elseif($datamode === Mode::NUMBER){ - $result .= Number::decodeSegment($this->bitBuffer, $versionNumber); + $result .= (new Number)->decodeSegment($this->bitBuffer, $versionNumber); } elseif($datamode === Mode::ALPHANUM){ $result .= $this->decodeAlphanumSegment($versionNumber, $fc1InEffect); } elseif($datamode === Mode::BYTE){ - $result .= Byte::decodeSegment($this->bitBuffer, $versionNumber); + $result .= (new Byte)->decodeSegment($this->bitBuffer, $versionNumber); } elseif($datamode === Mode::KANJI){ - $result .= Kanji::decodeSegment($this->bitBuffer, $versionNumber); + $result .= (new Kanji)->decodeSegment($this->bitBuffer, $versionNumber); } elseif($datamode === Mode::STRCTURED_APPEND){ @@ -143,10 +143,10 @@ final class Decoder{ $fc1InEffect = true; } elseif($datamode === Mode::ECI){ - $result .= ECI::decodeSegment($this->bitBuffer, $versionNumber); + $result .= (new ECI)->decodeSegment($this->bitBuffer, $versionNumber); } elseif($datamode === Mode::HANZI){ - $result .= Hanzi::decodeSegment($this->bitBuffer, $versionNumber); + $result .= (new Hanzi)->decodeSegment($this->bitBuffer, $versionNumber); } else{ throw new QRCodeDecoderException('invalid data mode'); @@ -167,7 +167,7 @@ final class Decoder{ } private function decodeAlphanumSegment(int $versionNumber, bool $fc1InEffect):string{ - $str = AlphaNum::decodeSegment($this->bitBuffer, $versionNumber); + $str = (new AlphaNum)->decodeSegment($this->bitBuffer, $versionNumber); // See section 6.4.8.1, 6.4.8.2 if($fc1InEffect){ // ??? diff --git a/tests/Data/DataInterfaceTestAbstract.php b/tests/Data/DataInterfaceTestAbstract.php index f9c33b7eb..ca5010e5a 100644 --- a/tests/Data/DataInterfaceTestAbstract.php +++ b/tests/Data/DataInterfaceTestAbstract.php @@ -89,7 +89,7 @@ abstract class DataInterfaceTestAbstract extends TestCase{ // read 4 bits (data mode indicator) $bitBuffer->read(4); // decode and check against the given string - $decoded = $dataModeInterface::decodeSegment($bitBuffer, 40); + $decoded = $dataModeInterface->decodeSegment($bitBuffer, 40); $this::assertSame($string, $decoded); } @@ -129,7 +129,7 @@ abstract class DataInterfaceTestAbstract extends TestCase{ // read the first 4 bits $this::assertSame($this->dataMode::DATAMODE, $bitBuffer->read(4)); // decode the data - $this::assertSame(static::testData, $this->dataMode::decodeSegment($bitBuffer, $options->version)); + $this::assertSame(static::testData, $this->dataMode->decodeSegment($bitBuffer, $options->version)); } /** @@ -187,7 +187,7 @@ abstract class DataInterfaceTestAbstract extends TestCase{ $bitBuffer = $this->QRData->getBitBuffer(); $this::assertSame($this->dataMode::DATAMODE, $bitBuffer->read(4)); - $this::assertSame($str, $this->dataMode::decodeSegment($bitBuffer, $options->version)); + $this::assertSame($str, $this->dataMode->decodeSegment($bitBuffer, $options->version)); } /** @@ -218,7 +218,7 @@ abstract class DataInterfaceTestAbstract extends TestCase{ // verify the encoded data $this::assertSame($this->dataMode::DATAMODE, $bitBuffer->read(4)); - $this::assertSame($str, $this->dataMode::decodeSegment($bitBuffer, $minimumVersionNumber)); + $this::assertSame($str, $this->dataMode->decodeSegment($bitBuffer, $minimumVersionNumber)); } /** diff --git a/tests/Data/ECITest.php b/tests/Data/ECITest.php index 9fbc796cf..2a5711986 100644 --- a/tests/Data/ECITest.php +++ b/tests/Data/ECITest.php @@ -67,7 +67,7 @@ final class ECITest extends TestCase{ // read the first 4 bits $this::assertSame($segments[0]::DATAMODE, $bitBuffer->read(4)); // decode the data - $this::assertSame(self::testData, ECI::decodeSegment($bitBuffer, $options->version)); + $this::assertSame(self::testData, (new ECI)->decodeSegment($bitBuffer, $options->version)); } #[Test] @@ -112,7 +112,7 @@ final class ECITest extends TestCase{ $this::assertSame($lengthInBits, $eci->getLengthInBits()); $this::assertSame(Mode::ECI, $bitBuffer->read(4)); - $this::assertSame($id, ECI::parseValue($bitBuffer)->getID()); + $this::assertSame($id, $eci->parseValue($bitBuffer)->getID()); } /** @@ -134,7 +134,7 @@ final class ECITest extends TestCase{ // verify the ECI mode indicator $this::assertSame(Mode::ECI, $bitBuffer->read(4)); // throw - ECI::decodeSegment($bitBuffer, $options->version); + (new ECI)->decodeSegment($bitBuffer, $options->version); } /** @@ -159,7 +159,7 @@ final class ECITest extends TestCase{ $segments = [new ECI($id), new Byte($data)]; $bitBuffer = (new QRData($options, $segments))->getBitBuffer(); $this::assertSame(Mode::ECI, $bitBuffer->read(4)); - $this::assertSame($data, ECI::decodeSegment($bitBuffer, $options->version)); + $this::assertSame($data,(new ECI)->decodeSegment($bitBuffer, $options->version)); } }