diff --git a/.php_cs b/.php_cs index 8c61b21..f094404 100644 --- a/.php_cs +++ b/.php_cs @@ -8,46 +8,51 @@ return \PhpCsFixer\Config::create() ->setRules([ '@PSR2' => true, - 'array_syntax' => [ - 'syntax' => 'short' - ], - 'binary_operator_spaces' => [ - 'align_double_arrow' => null, - 'align_equals' => false, - ], + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'backtick_to_shell_exec' => true, + 'binary_operator_spaces' => true, 'blank_line_after_opening_tag' => true, 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['method']], + 'combine_consecutive_issets' => true, 'combine_consecutive_unsets' => true, - 'concat_space' => [ - 'spacing' => 'one' - ], + 'concat_space' => ['spacing' => 'one'], 'declare_equal_normalize' => true, 'dir_constant' => true, 'ereg_to_preg' => true, + 'fully_qualified_strict_types' => true, 'function_to_constant' => true, 'function_typehint_space' => true, - 'hash_to_slash_comment' => true, 'heredoc_to_nowdoc' => true, 'include' => true, 'is_null' => true, + 'list_syntax' => ['syntax' => 'short'], + 'logical_operators' => true, 'lowercase_cast' => true, + 'lowercase_static_reference' => true, 'magic_constant_casing' => true, - 'method_separation' => true, 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => true, + 'native_constant_invocation' => true, 'native_function_casing' => true, + 'native_function_invocation' => ['include' => ['@compiler_optimized']], 'new_with_braces' => true, 'no_alias_functions' => true, + 'no_binary_string' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, 'no_empty_comment' => true, 'no_empty_phpdoc' => true, 'no_empty_statement' => true, - 'no_extra_consecutive_blank_lines' => true, + 'no_extra_blank_lines' => true, + 'no_homoglyph_names' => true, 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, 'no_mixed_echo_print' => true, 'no_multiline_whitespace_around_double_arrow' => true, - 'no_multiline_whitespace_before_semicolons' => true, 'no_php4_constructor' => true, 'no_short_bool_cast' => true, 'no_singleline_whitespace_before_semicolons' => true, @@ -55,6 +60,9 @@ return \PhpCsFixer\Config::create() 'no_trailing_comma_in_list_call' => true, 'no_trailing_comma_in_singleline_array' => true, 'no_unneeded_control_parentheses' => true, + 'no_unneeded_curly_braces' => true, + 'no_unneeded_final_method' => true, + 'no_unreachable_default_argument_value' => true, 'no_unused_imports' => true, 'no_useless_return' => true, 'no_whitespace_before_comma_in_array' => true, @@ -62,38 +70,53 @@ return \PhpCsFixer\Config::create() 'non_printable_character' => true, 'normalize_index_brace' => true, 'object_operator_without_whitespace' => true, - 'ordered_class_elements' => [ - 'order' => ['use_trait', 'constant', 'property', 'construct', 'method'], - ], + 'ordered_class_elements' => ['order' => ['use_trait', 'constant', 'property', 'construct', 'method']], 'ordered_imports' => true, 'php_unit_construct' => true, - 'php_unit_dedicate_assert' => true, + 'php_unit_dedicate_assert' => ['target' => 'newest'], + 'php_unit_expectation' => true, + 'php_unit_mock' => true, + 'php_unit_namespaced' => true, + 'php_unit_no_expectation_annotation' => true, + 'php_unit_set_up_tear_down_visibility' => true, 'php_unit_strict' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 'phpdoc_add_missing_param_annotation' => true, + 'phpdoc_align' => ['align' => 'left'], 'phpdoc_annotation_without_dot' => true, 'phpdoc_indent' => true, 'phpdoc_inline_tag' => true, 'phpdoc_no_access' => true, 'phpdoc_no_alias_tag' => true, 'phpdoc_no_package' => true, + 'phpdoc_no_useless_inheritdoc' => true, + 'phpdoc_return_self_reference' => true, 'phpdoc_scalar' => true, 'phpdoc_single_line_var_spacing' => true, 'phpdoc_summary' => true, + 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, + 'phpdoc_trim_consecutive_blank_line_separation' => true, 'phpdoc_types' => true, 'phpdoc_var_without_name' => true, 'pow_to_exponentiation' => true, 'psr4' => true, 'return_type_declaration' => true, 'self_accessor' => true, + 'semicolon_after_instruction' => true, + 'set_type_to_cast' => true, 'short_scalar_cast' => true, + 'simplified_null_return' => true, 'single_blank_line_before_namespace' => true, + 'single_line_comment_style' => true, 'single_quote' => true, 'space_after_semicolon' => true, + 'standardize_increment' => true, 'standardize_not_equals' => true, 'strict_comparison' => true, 'strict_param' => true, 'ternary_operator_spaces' => true, + 'ternary_to_null_coalescing' => true, 'trailing_comma_in_multiline_array' => true, 'trim_array_spaces' => true, 'whitespace_after_comma_in_array' => true, diff --git a/.travis.yml b/.travis.yml index 599b28f..fbb0919 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,31 +1,33 @@ language: php sudo: false -dist: trusty php: - 5.6 - 7.0 - 7.1 - - hhvm + - 7.2 cache: directories: - - vendor + - build/.composer-cache before_install: + - export COMPOSER_CACHE_DIR="$(pwd)/build/.composer-cache" - export XDEBUG="/home/travis/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini" - - is_hhvm () { [[ $TRAVIS_PHP_VERSION =~ ^hhvm ]]; } - - is_hhvm || mv -v "$XDEBUG" "$XDEBUG.disabled" + - mv -v "$XDEBUG" "$XDEBUG.disabled" install: - - travis_retry composer update -a --no-interaction - - travis_retry wget https://scrutinizer-ci.com/ocular.phar + - mkdir -p build/php_codesniffer build/php-cs-fixer build/ocular + - composer require --no-suggest --no-progress -n -a -d build/php-cs-fixer "friendsofphp/php-cs-fixer:^2.12" + - composer require --no-suggest --no-progress -n -a -d build/php_codesniffer "squizlabs/php_codesniffer:^3.3" + - composer require --no-suggest --no-progress -n -a -d build/ocular "scrutinizer/ocular:^1.5" + - composer update --no-suggest --no-progress -n -a script: - - vendor/bin/phpcs --standard=PSR2 src tests - - vendor/bin/php-cs-fixer fix -v --dry-run --allow-risky=yes --using-cache=no - - if is_hhvm; then echo "xdebug.enable = On" >> /etc/hhvm/php.ini; else mv -v "$XDEBUG.disabled" "$XDEBUG"; fi - - vendor/bin/phpunit --coverage-clover=coverage.clover --coverage-text + - build/php_codesniffer/vendor/bin/phpcs -p --standard=PSR2 src tests + - build/php-cs-fixer/vendor/bin/php-cs-fixer fix -v --dry-run --allow-risky=yes --using-cache=no + - mv -v "$XDEBUG.disabled" "$XDEBUG" + - vendor/bin/phpunit --coverage-clover=build/coverage.clover --coverage-text after_script: - - is_hhvm || php ocular.phar code-coverage:upload --format=php-clover coverage.clover + - build/ocular/vendor/bin/ocular code-coverage:upload --format=php-clover build/coverage.clover diff --git a/CHANGES.md b/CHANGES.md index 344c06a..d1e51c3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # Changelog # +## v2.4.0 (2018-07-03) ## + + * Added `string.classes` option, which allows to define an array of classes or + namespaces to encode using the `::class` format, when encountered as strings + * Added `string.imports` options, which allows to define the used imports to write + the `::class` format strings using shorter imported notation + * Support for HHVM has been dropped, as HHVM no longer aims for PHP compatibility + * Added travis builds for PHP 7.2 + * Change some rules in the used coding standard + ## v2.3.0 (2017-07-15) ## * Added `string.utf8` option which causes the string encoder to escape all diff --git a/LICENSE b/LICENSE index 7f2594c..74ef170 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2017 Riikka Kalliomäki +Copyright (c) 2013-2018 Riikka Kalliomäki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 385b8a5..f890a62 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,23 @@ apply to following calls. multibyte UTF-8 characters in strings are encoded using the PHP7 unicode code point syntax. Note that this syntax does not work in PHP versions earlier than 7.0. + + * **string.classes** : <string[]> ([])
+ Defines a list of classes or class namespace prefixes for classes that + can be encoded using the class resolution operator `::class` when + encountered in strings. e.g. providing the value `['Riimu\\']` would encode + all strings that look like class names in the `Riimu` namespace like + `Riimu\Kit\PHPEncoder::class`. + + * **string.imports** : <string[]> ([])
+ List of imports used in the resulting code file, which allows class names + to be written using shorter format. The list should be an associative array + with the namespace or class as the key and the used name as the value. Use + empty string to indicate the current namespace. + + For example, if the resulting file would have `namespace Riimu\Kit\PHPEncoder;` + and `use PHPUnit\Framework\TestCase;`, you could set up the array as + `['Riimu\\Kit\\PHPEncoder\\' => '', 'PHPUnit\\Framework\\TestCase' => 'TestCase']` * **array.short** : <boolean> (true)
When set to `true`, arrays are enclosed using square brackets `[]` instead @@ -279,6 +296,6 @@ on HHVM. ## Credits ## -This library is Copyright (c) 2013-2017 Riikka Kalliomäki. +This library is Copyright (c) 2013-2018 Riikka Kalliomäki. See LICENSE for license and copying information. diff --git a/composer.json b/composer.json index 03ce079..cacc5ce 100644 --- a/composer.json +++ b/composer.json @@ -22,9 +22,7 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.2", - "squizlabs/php_codesniffer": "^3.0", - "friendsofphp/php-cs-fixer": "^2.3" + "phpunit/phpunit": "^7.2 || ^6.5 || ^5.7" }, "suggest": { "ext-gmp": "To convert GMP numbers into PHP code" diff --git a/examples/class_resolution.php b/examples/class_resolution.php new file mode 100644 index 0000000..3cf0967 --- /dev/null +++ b/examples/class_resolution.php @@ -0,0 +1,37 @@ + [ + 'Riimu\\', + 'PHPUnit\\Framework\\TestCase', + 'DateTime', + ], + 'string.imports' => [ + 'Riimu\\Kit\\PHPEncoder\\' => '', + 'PHPUnit\\Framework\\TestCase' => 'TestCase', + ], +]); + + +echo "encode([ + \PHPUnit\Framework\TestCase::class, + \Riimu\Kit\PHPEncoder\PHPEncoder::class, + \Riimu\Kit\PHPEncoder\Encoder\Encoder::class, + \DateTime::class, + \DateTimeInterface::class, // Will be encoded as plain string, since it's not allowed by string.classes +]); + +echo ");\n"; diff --git a/src/Encoder/ArrayEncoder.php b/src/Encoder/ArrayEncoder.php index 2597b2c..e516fe3 100644 --- a/src/Encoder/ArrayEncoder.php +++ b/src/Encoder/ArrayEncoder.php @@ -5,20 +5,20 @@ /** * Encoder for array values. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class ArrayEncoder implements Encoder { /** @var array Default values for options in the encoder */ private static $defaultOptions = [ - 'array.short' => true, - 'array.base' => 0, + 'array.short' => true, + 'array.base' => 0, 'array.indent' => 4, - 'array.align' => false, + 'array.align' => false, 'array.inline' => 70, - 'array.omit' => true, - 'array.eol' => false, + 'array.omit' => true, + 'array.eol' => false, ]; public function getDefaultOptions() @@ -28,7 +28,7 @@ public function getDefaultOptions() public function supports($value) { - return is_array($value); + return \is_array($value); } public function encode($value, $depth, array $options, callable $encode) @@ -107,7 +107,7 @@ private function getInlineArray(array $lines, array $options) if (preg_match('/[\r\n\t]/', $output)) { return false; - } elseif ($options['array.inline'] === true || strlen($output) <= (int) $options['array.inline']) { + } elseif ($options['array.inline'] === true || \strlen($output) <= (int) $options['array.inline']) { return $output; } @@ -125,7 +125,7 @@ private function buildArray(array $lines, $depth, array $options) { $indent = $this->buildIndent($options['array.base'], $options['array.indent'], $depth + 1); $last = $this->buildIndent($options['array.base'], $options['array.indent'], $depth); - $eol = $options['array.eol'] === false ? PHP_EOL : (string) $options['array.eol']; + $eol = $options['array.eol'] === false ? \PHP_EOL : (string) $options['array.eol']; return $this->wrap( sprintf('%s%s%s,%1$s%s', $eol, $indent, implode(',' . $eol . $indent, $lines), $last), @@ -153,10 +153,10 @@ private function wrap($string, $short) */ private function buildIndent($base, $indent, $depth) { - $base = is_int($base) ? str_repeat(' ', $base) : (string) $base; + $base = \is_int($base) ? str_repeat(' ', $base) : (string) $base; return $depth === 0 ? $base : $base . str_repeat( - is_int($indent) ? str_repeat(' ', $indent) : (string) $indent, + \is_int($indent) ? str_repeat(' ', $indent) : (string) $indent, $depth ); } @@ -180,7 +180,7 @@ private function getAlignedPairs(array $array, callable $encode) $format = sprintf('%%-%ds => %%s', max(array_map('strlen', $keys))); $pairs = []; - for ($i = 0, $count = count($keys); $i < $count; $i++) { + for ($i = 0, $count = \count($keys); $i < $count; $i++) { $pairs[] = sprintf($format, $keys[$i], $values[$i]); } @@ -225,7 +225,7 @@ private function canOmitKey($key, & $nextIndex) { $result = $key === $nextIndex; - if (is_int($key)) { + if (\is_int($key)) { $nextIndex = max($key + 1, $nextIndex); } diff --git a/src/Encoder/BooleanEncoder.php b/src/Encoder/BooleanEncoder.php index 0797818..3f8350d 100644 --- a/src/Encoder/BooleanEncoder.php +++ b/src/Encoder/BooleanEncoder.php @@ -5,7 +5,7 @@ /** * Encoder for boolean values. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class BooleanEncoder implements Encoder @@ -22,7 +22,7 @@ public function getDefaultOptions() public function supports($value) { - return is_bool($value); + return \is_bool($value); } public function encode($value, $depth, array $options, callable $encode) diff --git a/src/Encoder/Encoder.php b/src/Encoder/Encoder.php index 0456bd1..fcbdd56 100644 --- a/src/Encoder/Encoder.php +++ b/src/Encoder/Encoder.php @@ -5,7 +5,7 @@ /** * Interface for different types of value encoders. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ interface Encoder diff --git a/src/Encoder/FloatEncoder.php b/src/Encoder/FloatEncoder.php index 2ca1ebf..9a8094a 100644 --- a/src/Encoder/FloatEncoder.php +++ b/src/Encoder/FloatEncoder.php @@ -5,7 +5,7 @@ /** * Encoder for float values. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class FloatEncoder implements Encoder @@ -15,7 +15,7 @@ class FloatEncoder implements Encoder /** @var array Default values for options in the encoder */ private static $defaultOptions = [ - 'float.integers' => false, + 'float.integers' => false, 'float.precision' => 17, 'float.export' => false, ]; @@ -27,7 +27,7 @@ public function getDefaultOptions() public function supports($value) { - return is_float($value); + return \is_float($value); } public function encode($value, $depth, array $options, callable $encode) @@ -86,9 +86,9 @@ private function isInteger($float, $allowIntegers) */ private function encodeInteger($float, callable $encode) { - $minimum = defined('PHP_INT_MIN') ? PHP_INT_MIN : ~PHP_INT_MAX; + $minimum = \defined('PHP_INT_MIN') ? \PHP_INT_MIN : ~\PHP_INT_MAX; - if ($float >= $minimum && $float <= PHP_INT_MAX) { + if ($float >= $minimum && $float <= \PHP_INT_MAX) { return $encode((int) $float); } @@ -105,7 +105,7 @@ private function determinePrecision($options) $precision = $options['float.precision']; if ($precision === false) { - $precision = defined('HHVM_VERSION') ? 17 : ini_get('serialize_precision'); + $precision = ini_get('serialize_precision'); } return max(1, (int) $precision); diff --git a/src/Encoder/GMPEncoder.php b/src/Encoder/GMPEncoder.php index 777004e..7386ec1 100644 --- a/src/Encoder/GMPEncoder.php +++ b/src/Encoder/GMPEncoder.php @@ -5,7 +5,7 @@ /** * Encoder for GMP number types. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class GMPEncoder implements Encoder @@ -17,7 +17,7 @@ public function getDefaultOptions() public function supports($value) { - return is_object($value) && get_class($value) === \GMP::class; + return \is_object($value) && \get_class($value) === \GMP::class; } public function encode($value, $depth, array $options, callable $encode) diff --git a/src/Encoder/IntegerEncoder.php b/src/Encoder/IntegerEncoder.php index 1ef6a5f..33eec60 100644 --- a/src/Encoder/IntegerEncoder.php +++ b/src/Encoder/IntegerEncoder.php @@ -5,7 +5,7 @@ /** * Encoder for integer values. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class IntegerEncoder implements Encoder @@ -46,7 +46,7 @@ public function getDefaultOptions() public function supports($value) { - return is_int($value); + return \is_int($value); } public function encode($value, $depth, array $options, callable $encode) @@ -88,7 +88,7 @@ public function encodeOctal($integer) */ public function encodeDecimal($integer, $options) { - if ($integer === 1 << (PHP_INT_SIZE * 8 - 1)) { + if ($integer === 1 << (\PHP_INT_SIZE * 8 - 1)) { return sprintf('(int)%s%d', $options['whitespace'] ? ' ' : '', $integer); } diff --git a/src/Encoder/NullEncoder.php b/src/Encoder/NullEncoder.php index ca6201d..1fd8672 100644 --- a/src/Encoder/NullEncoder.php +++ b/src/Encoder/NullEncoder.php @@ -5,7 +5,7 @@ /** * Encoder for null values. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class NullEncoder implements Encoder diff --git a/src/Encoder/ObjectEncoder.php b/src/Encoder/ObjectEncoder.php index dac1901..bdbfa5b 100644 --- a/src/Encoder/ObjectEncoder.php +++ b/src/Encoder/ObjectEncoder.php @@ -5,7 +5,7 @@ /** * Encoder for generic objects. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class ObjectEncoder implements Encoder @@ -14,7 +14,7 @@ class ObjectEncoder implements Encoder private static $defaultOptions = [ 'object.method' => true, 'object.format' => 'vars', - 'object.cast' => true, + 'object.cast' => true, ]; public function getDefaultOptions() @@ -24,7 +24,7 @@ public function getDefaultOptions() public function supports($value) { - return is_object($value); + return \is_object($value); } public function encode($value, $depth, array $options, callable $encode) @@ -54,7 +54,7 @@ private function encodeObject($object, array $options, callable $encode) } elseif ($options['object.format'] === 'serialize') { return sprintf('unserialize(%s)', $encode(serialize($object))); } elseif ($options['object.format'] === 'export') { - return sprintf('\\%s::__set_state(%s)', get_class($object), $encode($this->getObjectState($object))); + return sprintf('\\%s::__set_state(%s)', \get_class($object), $encode($this->getObjectState($object))); } return $this->encodeObjectArray($object, $options, $encode); @@ -70,7 +70,7 @@ private function encodeObject($object, array $options, callable $encode) */ private function encodeObjectArray($object, array $options, callable $encode) { - if (!in_array((string) $options['object.format'], ['array', 'vars', 'iterate'], true)) { + if (!\in_array((string) $options['object.format'], ['array', 'vars', 'iterate'], true)) { throw new \RuntimeException('Invalid object encoding format: ' . $options['object.format']); } diff --git a/src/Encoder/StringEncoder.php b/src/Encoder/StringEncoder.php index bc077ae..1fdcb78 100644 --- a/src/Encoder/StringEncoder.php +++ b/src/Encoder/StringEncoder.php @@ -5,7 +5,7 @@ /** * Encoder for string values. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class StringEncoder implements Encoder @@ -15,6 +15,8 @@ class StringEncoder implements Encoder 'string.escape' => true, 'string.binary' => false, 'string.utf8' => false, + 'string.classes' => [], + 'string.imports' => [], ]; public function getDefaultOptions() @@ -24,21 +26,90 @@ public function getDefaultOptions() public function supports($value) { - return is_string($value); + return \is_string($value); } public function encode($value, $depth, array $options, callable $encode) { $value = (string) $value; + if ($this->isClassName($value, $options)) { + return $this->getClassName($value, $options); + } + if (preg_match('/[^\x20-\x7E]/', $value)) { - if ($this->isBinaryString($value, $options)) { - return $this->encodeBinaryString($value); - } elseif ($options['string.escape']) { - return $this->getDoubleQuotedString($value, $options); + return $this->getComplexString($value, $options); + } + + return $this->getSingleQuotedString($value); + } + + /** + * Tests if the given value is a string that could be encoded as a class name constant. + * @param string $value The string to test + * @param array $options The string encoding options + * @return bool True if string can be encoded as class constant, false if not + */ + private function isClassName($value, array $options) + { + if (preg_match('/^([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(\\\\(?1))*$/', $value) !== 1) { + return false; + } + + return array_intersect(iterator_to_array($this->iterateNamespaces($value)), $options['string.classes']) !== []; + } + + /** + * Encodes the given string as a class name constant based on used imports. + * @param string $value The string to encode + * @param array $options The string encoding options + * @return string The class constant PHP code representation + */ + private function getClassName($value, array $options) + { + foreach ($this->iterateNamespaces($value) as $partial) { + if (isset($options['string.imports'][$partial])) { + $trimmed = substr($value, \strlen(rtrim($partial, '\\'))); + return ltrim(sprintf('%s%s::class', rtrim($options['string.imports'][$partial], '\\'), $trimmed), '\\'); } } + return sprintf('\\%s::class', $value); + } + + /** + * Iterates over the variations of the namespace for the given class name. + * @param string $value The class name to iterate over + * @return \Generator|string[] The namespace parts of the string + */ + private function iterateNamespaces($value) + { + yield $value; + + $parts = explode('\\', '\\' . $value); + $count = \count($parts); + + for ($i = 1; $i < $count; $i++) { + yield ltrim(implode('\\', \array_slice($parts, 0, -$i)), '\\') . '\\'; + } + } + + /** + * Returns the PHP code representation for the string that is not just simple ascii characters. + * @param string $value The string to encode + * @param array $options The string encoding options + * @return string The PHP code representation for the complex string + */ + private function getComplexString($value, array $options) + { + if ($this->isBinaryString($value, $options)) { + return $this->encodeBinaryString($value); + } + + if ($options['string.escape']) { + return $this->getDoubleQuotedString($value, $options); + } + return $this->getSingleQuotedString($value); } @@ -102,8 +173,8 @@ private function getDoubleQuotedString($string, $options) "\n" => '\n', "\r" => '\r', "\t" => '\t', - '$' => '\$', - '"' => '\"', + '$' => '\$', + '"' => '\"', '\\' => '\\\\', ]); @@ -112,7 +183,7 @@ private function getDoubleQuotedString($string, $options) } $hexFormat = function ($matches) use ($options) { - return sprintf($options['hex.capitalize'] ? '\x%02X' : '\x%02x', ord($matches[0])); + return sprintf($options['hex.capitalize'] ? '\x%02X' : '\x%02x', \ord($matches[0])); }; return sprintf('"%s"', preg_replace_callback('/[^\x20-\x7E]/', $hexFormat, $string)); @@ -147,20 +218,20 @@ private function encodeUtf8($string, $options) */ private function getCodePoint($bytes) { - if (strlen($bytes) === 2) { - return ((ord($bytes[0]) & 0b11111) << 6) - | (ord($bytes[1]) & 0b111111); + if (\strlen($bytes) === 2) { + return ((\ord($bytes[0]) & 0b11111) << 6) + | (\ord($bytes[1]) & 0b111111); } - if (strlen($bytes) === 3) { - return ((ord($bytes[0]) & 0b1111) << 12) - | ((ord($bytes[1]) & 0b111111) << 6) - | (ord($bytes[2]) & 0b111111); + if (\strlen($bytes) === 3) { + return ((\ord($bytes[0]) & 0b1111) << 12) + | ((\ord($bytes[1]) & 0b111111) << 6) + | (\ord($bytes[2]) & 0b111111); } - return ((ord($bytes[0]) & 0b111) << 18) - | ((ord($bytes[1]) & 0b111111) << 12) - | ((ord($bytes[2]) & 0b111111) << 6) - | (ord($bytes[3]) & 0b111111); + return ((\ord($bytes[0]) & 0b111) << 18) + | ((\ord($bytes[1]) & 0b111111) << 12) + | ((\ord($bytes[2]) & 0b111111) << 6) + | (\ord($bytes[3]) & 0b111111); } } diff --git a/src/InvalidOptionException.php b/src/InvalidOptionException.php index 4243378..5a7ec97 100644 --- a/src/InvalidOptionException.php +++ b/src/InvalidOptionException.php @@ -5,7 +5,7 @@ /** * Exception that gets thrown if invalid encoder options are used. * @author Riikka Kalliomäki - * @copyright Copyright (c) 2015-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2015-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class InvalidOptionException extends \InvalidArgumentException diff --git a/src/PHPEncoder.php b/src/PHPEncoder.php index 5d6bffb..412572f 100644 --- a/src/PHPEncoder.php +++ b/src/PHPEncoder.php @@ -11,7 +11,7 @@ * in specific way. * * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class PHPEncoder @@ -24,11 +24,11 @@ class PHPEncoder /** @var array Default values for options in the encoder */ private static $defaultOptions = [ - 'whitespace' => true, + 'whitespace' => true, 'recursion.detect' => true, 'recursion.ignore' => false, - 'recursion.max' => false, - 'hex.capitalize' => false, + 'recursion.max' => false, + 'hex.capitalize' => false, ]; /** @@ -230,6 +230,6 @@ private function encodeValue($value, $depth, array $options, callable $encode) } } - throw new \InvalidArgumentException(sprintf("Unsupported value type '%s'", gettype($value))); + throw new \InvalidArgumentException(sprintf("Unsupported value type '%s'", \gettype($value))); } } diff --git a/src/autoload.php b/src/autoload.php index 45ca83e..7e3bbb9 100644 --- a/src/autoload.php +++ b/src/autoload.php @@ -4,8 +4,8 @@ // Bundled autoloader provided as an optional alternative to composer autoloader spl_autoload_register(function ($class) { - if (strncmp($class, __NAMESPACE__, strlen(__NAMESPACE__)) === 0) { - $path = __DIR__ . strtr(substr($class, strlen(__NAMESPACE__)), ['\\' => DIRECTORY_SEPARATOR]) . '.php'; + if (strncmp($class, __NAMESPACE__, \strlen(__NAMESPACE__)) === 0) { + $path = __DIR__ . strtr(substr($class, \strlen(__NAMESPACE__)), ['\\' => \DIRECTORY_SEPARATOR]) . '.php'; if (file_exists($path)) { require $path; diff --git a/tests/tests/ArrayEncodingTest.php b/tests/tests/ArrayEncodingTest.php index b1c726f..3e45515 100644 --- a/tests/tests/ArrayEncodingTest.php +++ b/tests/tests/ArrayEncodingTest.php @@ -4,7 +4,7 @@ /** * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class ArrayEncodingTest extends EncodingTestCase @@ -53,7 +53,7 @@ public function testInlineWithMultiLineString() ] RESULT ), - ['foo' . PHP_EOL . 'bar'], + ['foo' . \PHP_EOL . 'bar'], ['string.escape' => false] ); } @@ -147,7 +147,7 @@ public function testWhitespace() "[\n1 => 1,\n0 => 0,\n]", [1 => 1, 0 => 0], [ - 'array.eol' => "\n", + 'array.eol' => "\n", 'array.indent' => 0, ] ); @@ -156,7 +156,7 @@ public function testWhitespace() "[\r1 => 1,\r0 => 0,\r]", [1 => 1, 0 => 0], [ - 'array.eol' => "\r", + 'array.eol' => "\r", 'array.indent' => 0, ] ); @@ -172,7 +172,7 @@ public function testWhitespace() ), ['foo' => 'bar', 1 => true], [ - 'array.base' => 1, + 'array.base' => 1, 'array.indent' => 2, ] ); @@ -188,7 +188,7 @@ public function testWhitespace() ), ['foo' => 'bar', 1 => true], [ - 'array.base' => "\t", + 'array.base' => "\t", 'array.indent' => "\t", ] ); @@ -351,7 +351,7 @@ public function testInlineOnAlignedArray() private function format($string, $eol = false) { if ($eol === false) { - $eol = PHP_EOL; + $eol = \PHP_EOL; } return preg_replace('/\r\n|\r|\n/', $eol, $string); diff --git a/tests/tests/EncodingTest.php b/tests/tests/EncodingTest.php index 52b5778..fee5d9f 100644 --- a/tests/tests/EncodingTest.php +++ b/tests/tests/EncodingTest.php @@ -7,7 +7,7 @@ /** * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class EncodingTest extends EncodingTestCase @@ -62,13 +62,13 @@ public function testIntegerEncoding() public function testMaximumInteger() { - $this->assertEncode((string) PHP_INT_MAX, PHP_INT_MAX); + $this->assertEncode((string) \PHP_INT_MAX, \PHP_INT_MAX); } public function testMinimumInteger() { - $this->assertEncode('(int) ' . (-PHP_INT_MAX - 1), -PHP_INT_MAX - 1); - $this->assertEncode('(int)' . (-PHP_INT_MAX - 1), -PHP_INT_MAX - 1, ['whitespace' => false]); + $this->assertEncode('(int) ' . (-\PHP_INT_MAX - 1), -\PHP_INT_MAX - 1); + $this->assertEncode('(int)' . (-\PHP_INT_MAX - 1), -\PHP_INT_MAX - 1, ['whitespace' => false]); } public function testIntegerTypes() @@ -121,10 +121,6 @@ public function testFloatExponents() public function testUsingIniPrecision() { - if (defined('HHVM_VERSION')) { - $this->markTestSkipped(); - } - $float = 1.1234567890123456; $serialize = ini_set('serialize_precision', 13); @@ -135,13 +131,13 @@ public function testUsingIniPrecision() public function testInfiniteFloat() { - $this->assertEncode('INF', INF); - $this->assertEncode('-INF', -INF); + $this->assertEncode('INF', \INF); + $this->assertEncode('-INF', -\INF); } public function testNanFloat() { - $code = (new PHPEncoder())->encode(NAN); + $code = (new PHPEncoder())->encode(\NAN); $this->assertSame('NAN', $code); $value = eval("return $code;"); @@ -248,7 +244,7 @@ public function testUtf8String() $this->assertEncode('"\nA"', "\nA", $encoder); $this->assertSame('"\nA\u{c4}\x00"', $encoder->encode("\nAÄ\x00")); - if (version_compare(PHP_VERSION, '7', '<')) { + if (version_compare(\PHP_VERSION, '7', '<')) { $this->assertSame('"\u{a2}"', $encoder->encode("\xC2\xA2")); $this->assertSame('"\u{20ac}"', $encoder->encode("\xE2\x82\xAC")); $this->assertSame('"\u{10348}"', $encoder->encode("\xF0\x90\x8D\x88")); @@ -264,9 +260,38 @@ public function testUtf8String() $this->assertSame('"\nA\u{C4}\x00"', $encoder->encode("\nAÄ\x00")); } + public function testClassStrings() + { + $encoder = new PHPEncoder(['string.classes' => [self::class]]); + $this->assertEncode('\\' . self::class . '::class', self::class, $encoder); + } + + public function testImportedClassString() + { + $encoder = new PHPEncoder([ + 'string.classes' => [\DateTime::class, PHPEncoder::class, FloatEncoder::class, Encoder::class], + 'string.imports' => ['\\' => '', __NAMESPACE__ . '\\' => 'Encoder', Encoder::class => 'EncoderInterface'], + ]); + + $this->assertSame('DateTime::class', $encoder->encode(\DateTime::class)); + $this->assertSame('Encoder\PHPEncoder::class', $encoder->encode(PHPEncoder::class)); + $this->assertSame('Encoder\Encoder\FloatEncoder::class', $encoder->encode(FloatEncoder::class)); + $this->assertSame('EncoderInterface::class', $encoder->encode(Encoder::class)); + $this->assertSame("'DateTimeInterface'", $encoder->encode(\DateTimeInterface::class)); + + $encoder = new PHPEncoder([ + 'string.classes' => [self::class], + 'string.imports' => [__NAMESPACE__ . '\\' => ''], + ]); + + $code = sprintf('namespace ' . __NAMESPACE__ . '; return %s;', $encoder->encode(self::class)); + $this->assertSame('namespace Riimu\Kit\PHPEncoder; return EncodingTest::class;', $code); + $this->assertSame(self::class, eval($code)); + } + public function testGMPEncoding() { - if (!function_exists('gmp_init')) { + if (!\function_exists('gmp_init')) { $this->markTestSkipped('Missing GMP library'); } @@ -349,7 +374,7 @@ public function testMaxDeathOnNoRecursionDetection() $encoder = new PHPEncoder([ 'recursion.detect' => false, - 'recursion.max' => 5, + 'recursion.max' => 5, ]); $this->expectException(\RuntimeException::class); diff --git a/tests/tests/EncodingTestCase.php b/tests/tests/EncodingTestCase.php index ce5e0ba..b69a508 100644 --- a/tests/tests/EncodingTestCase.php +++ b/tests/tests/EncodingTestCase.php @@ -6,7 +6,7 @@ /** * @author Riikka Kalliomäki - * @copyright Copyright (c) 2015-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2015-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class EncodingTestCase extends TestCase @@ -20,11 +20,11 @@ class EncodingTestCase extends TestCase */ protected function assertEncode($code, $value, $encoder = [], $initial = null) { - if (is_array($encoder)) { + if (\is_array($encoder)) { $encoder = new PHPEncoder($encoder); } - $output = $encoder->encode(func_num_args() < 4 ? $value : $initial); + $output = $encoder->encode(\func_num_args() < 4 ? $value : $initial); $this->assertSame($code, $output); $this->assertSame($value, eval("return $output;")); } diff --git a/tests/tests/ObjectEncodingTest.php b/tests/tests/ObjectEncodingTest.php index cb855b2..53758ff 100644 --- a/tests/tests/ObjectEncodingTest.php +++ b/tests/tests/ObjectEncodingTest.php @@ -9,7 +9,7 @@ /** * @author Riikka Kalliomäki - * @copyright Copyright (c) 2014-2017 Riikka Kalliomäki + * @copyright Copyright (c) 2014-2018 Riikka Kalliomäki * @license http://opensource.org/licenses/mit-license.php MIT License */ class ObjectEncodingTest extends TestCase @@ -28,7 +28,7 @@ public function testSerialize() ); $evaluated = eval("return $code;"); - $this->assertSame(get_class($obj), get_class($evaluated)); + $this->assertSame(\get_class($obj), \get_class($evaluated)); $this->assertSame((array) $obj, (array) $evaluated); } @@ -72,18 +72,18 @@ public function testPHP() public function testObjectVarsArray() { $encoder = new PHPEncoder([ - 'whitespace' => false, + 'whitespace' => false, 'object.format' => 'vars', - 'object.cast' => false, + 'object.cast' => false, ]); $std = new \stdClass(); $std->baz = 'C'; $this->assertSame("['baz'=>'C']", $encoder->encode($std)); $this->assertSame("(object) [\n 'baz' => 'C',\n]", $encoder->encode($std, [ - 'object.cast' => true, - 'whitespace' => true, - 'array.eol' => "\n", + 'object.cast' => true, + 'whitespace' => true, + 'array.eol' => "\n", 'array.indent' => ' ', ])); } @@ -92,7 +92,7 @@ public function testObjectExport() { $encoder = new PHPEncoder([ 'object.format' => 'export', - 'whitespace' => false, + 'whitespace' => false, ]); $obj = new ExtendsTestMockObject(); @@ -107,9 +107,9 @@ public function testObjectExport() public function testIteratingArray() { $encoder = new PHPEncoder([ - 'whitespace' => false, + 'whitespace' => false, 'object.format' => 'iterate', - 'object.cast' => false, + 'object.cast' => false, ]); $array = ['foo' => 'bar', [1, 2], 3, 10 => 1337, 11 => 7, 6 => 6]; @@ -120,9 +120,9 @@ public function testIteratingArray() public function testArrayCasting() { $encoder = new PHPEncoder([ - 'whitespace' => false, + 'whitespace' => false, 'object.format' => 'array', - 'object.cast' => false, + 'object.cast' => false, ]); $obj = new \stdClass();