Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move mbstring operations to separate handler class #236

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@
"prefer-stable": true,
"require": {
"php": "^8.2",
"ext-mbstring": "*",
"chillerlan/php-settings-container": "^3.1"
},
"require-dev": {
"ext-iconv": "*",
"ext-mbstring": "*",
"chillerlan/php-authenticator": "^5.1",
"phan/phan": "^5.4",
"phpunit/phpunit": "^10.5",
Expand All @@ -55,7 +56,8 @@
"suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
"setasign/fpdf": "Required to use the QR FPDF output.",
"simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code"
"simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code",
"symfony/polyfill-mbstring": "^1.24"
},
"autoload": {
"psr-4": {
Expand Down
35 changes: 35 additions & 0 deletions src/Common/CharacterEncodingHandlerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
/**
* Interface CharacterEncodingHandlerInterface
*
* @created 11.01.2024
* @author smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*/

namespace chillerlan\QRCode\Common;

/**
* Handles all multibyte character encoding/conversion operations
*/
interface CharacterEncodingHandlerInterface{

/**
* Get the string length
*/
public static function getCharCount(string $string, string $encoding = null):int;

/**
* Gets the internal character encoding
*/
public static function getInternalEncoding():string;

/**
* Converts the given `$string` to `$to_encoding`, optionally using the given encoding(s) in `$from_encoding`
*
* @throws \RuntimeException if an error occurred
*/
public static function convertEncoding(string $string, string $to_encoding, array|string $from_encoding = null):string;

}
48 changes: 48 additions & 0 deletions src/Common/EncodingHandlerTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* Trait EncodingHandlerTrait
*
* @created 12.01.2024
* @author smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*/

namespace chillerlan\QRCode\Common;

use chillerlan\QRCode\QRCodeException;
use Symfony\Polyfill\Mbstring\Mbstring;
use Throwable;
use function class_exists;
use function extension_loaded;

trait EncodingHandlerTrait{

protected static CharacterEncodingHandlerInterface $encodingHandler;

/**
* @throws \chillerlan\QRCode\QRCodeException
*/
protected static function getEncodingHandler():string{

try{
return match(true){
extension_loaded('mbstring') => MBStringHandler::class,
extension_loaded('iconv') && class_exists(Mbstring::class) => MBStringHandler::class,
extension_loaded('iconv') => IconvHandler::class,
};
}
catch(Throwable){
throw new QRCodeException('no character encoding handler available');
}

}

/**
* We're setting an instance here so that the IDE stops yelling at the FQN
*/
protected function setEncodingHandler():void{
static::$encodingHandler = new (static::getEncodingHandler());
}

}
60 changes: 60 additions & 0 deletions src/Common/IconvHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
/**
* Class IconvHandler
*
* @created 12.01.2024
* @author smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/

namespace chillerlan\QRCode\Common;

use RuntimeException;
use function iconv_get_encoding;
use function iconv_strlen;
use function is_array;
use function is_string;

/**
* Handles iconv encoding operations (will probably fail)
*/
class IconvHandler implements CharacterEncodingHandlerInterface{

/**
* @inheritDoc
*/
public static function getCharCount(string $string, string $encoding = null):int{
return iconv_strlen($string, $encoding);
}

/**
* @inheritDoc
*/
public static function getInternalEncoding():string{
return iconv_get_encoding('internal_encoding');
}

/**
* @inheritDoc
* @todo
*/
public static function convertEncoding(string $string, string $to_encoding, array|string $from_encoding = null):string{

// we don't have detect_encoding here, so we pick the first item, otherwise set to internal
if(is_array($from_encoding)){
$from_encoding = ($from_encoding[0] ?? self::getInternalEncoding());
}

$str = iconv($from_encoding, $to_encoding, $string);

if(!is_string($str)){
throw new RuntimeException('iconv error');
}

return $str;
}

}
54 changes: 54 additions & 0 deletions src/Common/MBStringHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* Class MBStringHandler
*
* @created 11.01.2024
* @author smiley <[email protected]>
* @copyright 2024 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/

namespace chillerlan\QRCode\Common;

use RuntimeException;
use function is_string;
use function mb_convert_encoding;
use function mb_internal_encoding;
use function mb_strlen;

/**
* Handles mbstring encoding operations
*/
class MBStringHandler implements CharacterEncodingHandlerInterface{

/**
* @inheritDoc
*/
public static function getCharCount(string $string, string $encoding = null):int{
return mb_strlen($string, $encoding);
}

/**
* @inheritDoc
*/
public static function getInternalEncoding():string{
return mb_internal_encoding();
}

/**
* @inheritDoc
* @see \mb_detect_encoding()
*/
public static function convertEncoding(string $string, string $to_encoding, array|string $from_encoding = null):string{
$str = mb_convert_encoding($string, $to_encoding, $from_encoding);

if(!is_string($str)){
throw new RuntimeException('mb_convert_encoding error');
}

return $str;
}

}
17 changes: 9 additions & 8 deletions src/Data/ECI.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

namespace chillerlan\QRCode\Data;

use chillerlan\QRCode\Common\{BitBuffer, ECICharset, Mode};
use function mb_convert_encoding, mb_detect_encoding, mb_internal_encoding, sprintf;
use chillerlan\QRCode\Common\{BitBuffer, ECICharset, EncodingHandlerTrait, Mode};
use function sprintf;

/**
* Adds an ECI Designator
Expand All @@ -21,6 +21,7 @@
* Please note that you have to take care for the correct data encoding when adding with QRCode::add*Segment()
*/
final class ECI extends QRDataModeAbstract{
use EncodingHandlerTrait;

/**
* @inheritDoc
Expand All @@ -43,6 +44,8 @@ public function __construct(int $encoding){
}

$this->encoding = $encoding;

$this->setEncodingHandler();
}

/**
Expand Down Expand Up @@ -142,14 +145,12 @@ public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):s
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
$encoding = mb_detect_encoding($data, ['ISO-8859-1', 'Windows-1252', 'SJIS', 'UTF-8'], true);

if($encoding === false){
throw new QRCodeDataException('could not determine encoding in ECI mode'); // @codeCoverageIgnore
}
$encoding = ['ISO-8859-1', 'Windows-1252', 'SJIS', 'UTF-8'];
}

return mb_convert_encoding($data, mb_internal_encoding(), $encoding);
$to_encoding = self::$encodingHandler::getInternalEncoding();

return self::$encodingHandler::convertEncoding($data, $to_encoding, $encoding);
}

}
29 changes: 7 additions & 22 deletions src/Data/Hanzi.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@

use chillerlan\QRCode\Common\{BitBuffer, Mode};
use Throwable;
use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding,
mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen;
use function chr, implode, intdiv, ord, sprintf, strlen;

/**
* Hanzi (simplified Chinese) mode, GBT18284-2000: 13-bit double-byte characters from the GB2312/GB18030 character set
Expand Down Expand Up @@ -52,7 +51,7 @@ final class Hanzi extends QRDataModeAbstract{
* @inheritDoc
*/
protected function getCharCount():int{
return mb_strlen($this->data, self::ENCODING);
return self::$encodingHandler::getCharCount($this->data, self::ENCODING);
}

/**
Expand All @@ -66,25 +65,9 @@ public function getLengthInBits():int{
* @inheritDoc
*/
public static function convertEncoding(string $string):string{
mb_detect_order([mb_internal_encoding(), 'UTF-8', 'GB2312', 'GB18030', 'CP936', 'EUC-CN', 'HZ']);
$encodings = [self::$encodingHandler::getInternalEncoding(), 'UTF-8', 'GB2312', 'GB18030', 'CP936', 'EUC-CN', 'HZ'];

$detected = mb_detect_encoding($string, null, true);

if($detected === false){
throw new QRCodeDataException('mb_detect_encoding error');
}

if($detected === self::ENCODING){
return $string;
}

$string = mb_convert_encoding($string, self::ENCODING, $detected);

if(!is_string($string)){
throw new QRCodeDataException('mb_convert_encoding error');
}

return $string;
return self::$encodingHandler::convertEncoding($string, self::ENCODING, $encodings);
}

/**
Expand Down Expand Up @@ -199,7 +182,9 @@ public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):s
$length--;
}

return mb_convert_encoding(implode($buffer), mb_internal_encoding(), self::ENCODING);
$to_encoding = self::$encodingHandler::getInternalEncoding();

return self::$encodingHandler::convertEncoding(implode($buffer), $to_encoding, self::ENCODING);
}

}
29 changes: 7 additions & 22 deletions src/Data/Kanji.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@

use chillerlan\QRCode\Common\{BitBuffer, Mode};
use Throwable;
use function chr, implode, intdiv, is_string, mb_convert_encoding, mb_detect_encoding,
mb_detect_order, mb_internal_encoding, mb_strlen, ord, sprintf, strlen;
use function chr, implode, intdiv, ord, sprintf, strlen;

/**
* Kanji mode: 13-bit double-byte characters from the Shift-JIS character set
Expand Down Expand Up @@ -45,7 +44,7 @@ final class Kanji extends QRDataModeAbstract{
* @inheritDoc
*/
protected function getCharCount():int{
return mb_strlen($this->data, self::ENCODING);
return self::$encodingHandler::getCharCount($this->data, self::ENCODING);
}

/**
Expand All @@ -59,25 +58,9 @@ public function getLengthInBits():int{
* @inheritDoc
*/
public static function convertEncoding(string $string):string{
mb_detect_order([mb_internal_encoding(), 'UTF-8', 'SJIS', 'SJIS-2004']);
$encodings = [self::$encodingHandler::getInternalEncoding(), 'UTF-8', 'SJIS', 'SJIS-2004'];

$detected = mb_detect_encoding($string, null, true);

if($detected === false){
throw new QRCodeDataException('mb_detect_encoding error');
}

if($detected === self::ENCODING){
return $string;
}

$string = mb_convert_encoding($string, self::ENCODING, $detected);

if(!is_string($string)){
throw new QRCodeDataException(sprintf('invalid encoding: %s', $detected));
}

return $string;
return self::$encodingHandler::convertEncoding($string, self::ENCODING, $encodings);
}

/**
Expand Down Expand Up @@ -185,7 +168,9 @@ public static function decodeSegment(BitBuffer $bitBuffer, int $versionNumber):s
$length--;
}

return mb_convert_encoding(implode($buffer), mb_internal_encoding(), self::ENCODING);
$to_encoding = self::$encodingHandler::getInternalEncoding();

return self::$encodingHandler::convertEncoding(implode($buffer), $to_encoding, self::ENCODING);
}

}
Loading
Loading