From 933ab870fedf07232210dd222e27d3db8dd646a9 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Mon, 8 Apr 2019 19:01:18 +0200 Subject: [PATCH 001/129] Fix static properties being treated like non-static --- src/Internal/ObjectExporter/AnyObjectExporter.php | 4 ++++ tests/Classes/NoProperties.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index 046c76e..61569a1 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -48,6 +48,10 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $unsetNonPublicProperties = []; foreach ($current->getProperties() as $property) { + if ($property->isStatic()) { + continue; + } + if ($isParentClass && ! $property->isPrivate()) { // property already handled in the child class. continue; diff --git a/tests/Classes/NoProperties.php b/tests/Classes/NoProperties.php index dd74c50..5edc5d0 100644 --- a/tests/Classes/NoProperties.php +++ b/tests/Classes/NoProperties.php @@ -9,4 +9,6 @@ */ class NoProperties { + // Static property should not be returned in the output + public static $staticProp = []; } From 581abba485c197b10aebdf3fc9f871ec76751e91 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 9 Apr 2019 00:07:09 +0200 Subject: [PATCH 002/129] Do not use echo in closure tests We're actually running exported closures in tests now. --- tests/ExportClosureTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index c529428..d78ca3c 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -23,12 +23,12 @@ class ExportClosureTest extends AbstractTestCase public function testExportSimpleClosure() { $var = function() { - echo 'Hello, world!'; + return 'Hello, world!'; }; $expected = <<<'PHP' function () { - echo 'Hello, world!'; + return 'Hello, world!'; } PHP; @@ -158,7 +158,7 @@ public function testExportClosureDefinedInEval() { $var = eval(<<assertExportThrows('Class "Closure" is internal, and cannot be exported.', $var, VarExporter::NO_CLOSURES); From fbdc00ecefb5e32d115965579b3952932cad8053 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 9 Apr 2019 00:14:23 +0200 Subject: [PATCH 003/129] Allow php-parser v4.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 076ffe2..87a2efc 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "license": "MIT", "require": { "php": ">=7.1", - "nikic/php-parser": "^4.2" + "nikic/php-parser": "^4.0" }, "require-dev": { "phpunit/phpunit": "^7.0", From 1910b9e20b8b9253b22bfb374a8f87d6d18f9d30 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 9 Apr 2019 00:16:53 +0200 Subject: [PATCH 004/129] Travis CI tests with lowest dependencies --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 26a633d..a7eb507 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,13 @@ php: - 7.3 - 7.4snapshot +matrix: + include: + - php: 7.1.0 + env: dependencies=lowest + before_script: - - composer install + - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable; else composer update; fi; script: - mkdir -p build/logs From 2c493100857217c310e846ffa6d05d6363ac5333 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 9 Apr 2019 00:21:06 +0200 Subject: [PATCH 005/129] Allow failures on PHP 7.4snapshot --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a7eb507..6969344 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,8 @@ matrix: include: - php: 7.1.0 env: dependencies=lowest + allow_failures: + - php: 7.4snapshot before_script: - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable; else composer update; fi; From 3394570370dfd79830be0f07723cefe8357c398d Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 9 Apr 2019 11:52:43 +0200 Subject: [PATCH 006/129] Update example version in README [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ccf4f25..741ebf2 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The current releases are numbered `0.x.y`. When a non-breaking change is introdu **When a breaking change is introduced, a new `0.x` version cycle is always started.** -It is therefore safe to lock your project to a given release cycle, such as `0.1.*`. +It is therefore safe to lock your project to a given release cycle, such as `0.2.*`. If you need to upgrade to a newer release cycle, check the [release history](https://github.com/brick/varexporter/releases) for a list of changes introduced by each further `0.x.0` version. From d17c7075fb96d3f695ed5ddd16205e23dfd86177 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 9 Apr 2019 11:53:50 +0200 Subject: [PATCH 007/129] Update project description [skip ci] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 87a2efc..fe790c3 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "brick/varexporter", - "description": "A pretty alternative to var_export(), which can export objects without __set_state()", + "description": "A powerful alternative to var_export(), which can export closures and objects without __set_state()", "type": "library", "keywords": [ "var_export" From 4068152bce6eedd15ff09431186f8e7d21335d0b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 9 Apr 2019 12:03:52 +0200 Subject: [PATCH 008/129] README tweaks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 741ebf2..a706f0a 100644 --- a/README.md +++ b/README.md @@ -336,7 +336,7 @@ Note how all namespaced classes, and explicitly namespaced functions and constan ### Caveats -- **Functions and constants that are not not explicitly namespaced**, either directly or through a `use function` or `use const` statement, **are always exported as is**. This is because the parser does not have the runtime context to check if a definition for this function exists in the current namespace. Be really careful here if you're relying on PHP's [fallback to global function/constant](https://www.php.net/manual/en/language.namespaces.fallback.php), and always explicitly import your functions in this case. +- **Functions and constants that are not not explicitly namespaced**, either directly or through a `use function` or `use const` statement, **are always exported as is**. This is because the parser does not have the runtime context to check if a definition for this function or constant exists in the current namespace, and as such cannot reliably predict the behaviour of PHP's [fallback to global function/constant](https://www.php.net/manual/en/language.namespaces.fallback.php). Be really careful here if you're using namespaced functions or constants: **always explicitly import your namespaced functions and constants**, if any. - **Closures that have variables bound through `use()` cannot be exported**, and will throw an `ExportException`. This is intentional, because exported closures can be executed in another context, and as such must not rely on the context they've been originally defined in. - Closures can use `$this`, but **will not be bound to an object once exported**. You must explicitly bind them through [`bindTo()`](https://www.php.net/manual/en/closure.bindto.php) if required, after running the exported code. - **You cannot have 2 closures on the same line in your source file**, or an `ExportException` will be thrown. This is because `VarExporter` cannot know which one holds the definition for the `\Closure` object it encountered. From 7fae7b32c74771a3e379cbcb29a8fe8677510360 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 9 Apr 2019 13:45:02 +0200 Subject: [PATCH 009/129] Notes about eval() and SuperClosure --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a706f0a..4a98a9e 100644 --- a/README.md +++ b/README.md @@ -301,7 +301,7 @@ echo VarExporter::export([ ] ``` -To do this magic, `VarExporter` parses the PHP source file where your closure is defined, using the well-established [nikic/php-parser](https://github.com/nikic/PHP-Parser) library. +To do this magic, `VarExporter` parses the PHP source file where your closure is defined, using the well-established [nikic/php-parser](https://github.com/nikic/PHP-Parser) library, inspired by [SuperClosure](https://github.com/jeremeamia/super_closure). To ensure that the closure will work in any context, it rewrites its source code, replacing any namespaced class/function/constant name with its fully qualified counterpart: @@ -340,6 +340,7 @@ Note how all namespaced classes, and explicitly namespaced functions and constan - **Closures that have variables bound through `use()` cannot be exported**, and will throw an `ExportException`. This is intentional, because exported closures can be executed in another context, and as such must not rely on the context they've been originally defined in. - Closures can use `$this`, but **will not be bound to an object once exported**. You must explicitly bind them through [`bindTo()`](https://www.php.net/manual/en/closure.bindto.php) if required, after running the exported code. - **You cannot have 2 closures on the same line in your source file**, or an `ExportException` will be thrown. This is because `VarExporter` cannot know which one holds the definition for the `\Closure` object it encountered. +- **Closures defined in eval()'d code cannot be exported** and throw an `ExportException`, because there is no source file to parse. You can disable exporting closures, using the [`NO_CLOSURES`](#varexporterno_closures) option. When this option is set, an `ExportException` will be thrown when attempting to export a closure. From 34b8e6bc1a2fed59cac4d5d91d7d5ae7bf7ac98b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 17 Apr 2019 00:22:12 +0200 Subject: [PATCH 010/129] Option to inline numeric scalar arrays --- src/Internal/GenericExporter.php | 66 ++++++++++++++++++++++++-------- src/VarExporter.php | 6 +++ tests/VarExporterTest.php | 21 ++++++++++ 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index b09f85f..dfaabcd 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -43,6 +43,11 @@ final class GenericExporter */ public $skipDynamicProperties; + /** + * @var bool + */ + public $inlineNumericScalarArray; + /** * @param int $options */ @@ -68,8 +73,9 @@ public function __construct(int $options) $this->objectExporters[] = new ObjectExporter\AnyObjectExporter($this); } - $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); - $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); + $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); + $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); + $this->inlineNumericScalarArray = (bool) ($options & VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); } /** @@ -123,13 +129,13 @@ public function exportArray(array $array, array $path, array $parents) : array $result = []; - $result[] = '['; - $count = count($array); $isNumeric = array_keys($array) === range(0, $count - 1); $current = 0; + $inline = ($this->inlineNumericScalarArray && $isNumeric && $this->isScalarArray($array)); + foreach ($array as $key => $value) { $isLast = (++$current === $count); @@ -138,28 +144,58 @@ public function exportArray(array $array, array $path, array $parents) : array $exported = $this->export($value, $newPath, $parents); - $prepend = ''; - $append = ''; + if ($inline) { + $result[] = $exported[0]; + } else { + $prepend = ''; + $append = ''; - if (! $isNumeric) { - $prepend = var_export($key, true) . ' => '; - } + if (! $isNumeric) { + $prepend = var_export($key, true) . ' => '; + } - if (! $isLast) { - $append = ','; - } + if (! $isLast) { + $append = ','; + } + + $exported = $this->wrap($exported, $prepend, $append); + $exported = $this->indent($exported); - $exported = $this->wrap($exported, $prepend, $append); - $exported = $this->indent($exported); + $result = array_merge($result, $exported); + } + } - $result = array_merge($result, $exported); + if ($inline) { + return ['[' . implode(', ', $result) . ']']; } + array_unshift($result, '['); $result[] = ']'; return $result; } + /** + * Returns whether the given array only contains scalar values. + * + * Types considered scalar here are int, bool, float, string and null. + * If the array is empty, this method returns true. + * + * @param array $array + * + * @return bool + */ + private function isScalarArray(array $array) : bool + { + foreach ($array as $value) { + if ($value !== null && ! is_scalar($value)) { + return false; + } + } + + return true; + } + /** * @param object $object The object to export. * @param string[] $path The path to the current object in the array/object graph. diff --git a/src/VarExporter.php b/src/VarExporter.php index 3680d84..7431abc 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -47,6 +47,12 @@ final class VarExporter */ public const NO_CLOSURES = 1 << 6; + /** + * Formats numeric arrays containing only scalar values on a single line. + * Types considered scalar here are int, bool, float, string and null. + */ + public const INLINE_NUMERIC_SCALAR_ARRAY = 1 << 7; + /** * @param mixed $var The variable to export. * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index e037238..c4bbf58 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -130,6 +130,27 @@ public function testAddReturn() $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN); } + public function testInlineNumericScalarArray() + { + $var = [ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => ['hello', 'world', ['one', 'two', 'three']] + ]; + + $expected = <<<'PHP' +[ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => [ + 'hello', + 'world', + ['one', 'two', 'three'] + ] +] +PHP; + + $this->assertExportEquals($expected, $var, VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); + } + public function testExportInternalClass() { $object = new \stdClass; From 8d76720eb107ae7bec7ddc38ce36d3b1de175efc Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 17 Apr 2019 01:03:05 +0200 Subject: [PATCH 011/129] Update README --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 4a98a9e..69b3ebb 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,30 @@ Disallows exporting any custom object using direct property access and bound clo Disallows exporting closures. +### `VarExporter::INLINE_NUMERIC_SCALAR_ARRAY` + +Formats numeric arrays containing only scalar values on a single line: + +```php +VarExporter::export([ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => ['hello', 'world', ['one', 'two', 'three']] +], VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); +``` + +```php +[ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => [ + 'hello', + 'world', + ['one', 'two', 'three'] + ] +] +``` + +Types considered scalar here are `int`, `bool`, `float`, `string` and `null`. + ## Error handling Any error occurring on `export()` will throw an `ExportException`: From 7587a586bb6595c061f03bbec8e7da3223ee6fc6 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 12 Jun 2019 19:22:46 +0200 Subject: [PATCH 012/129] Note about var_export()ing stdClass objects on PHP 7.3 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 69b3ebb..30248d3 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,8 @@ echo VarExporter::export(json_decode(' ] ``` +**Note: since PHP 7.3, `var_export()` now exports an array to object cast like `VarExporter::export()` does.** + ## Exporting custom objects As we've seen above, `var_export()` assumes that every object has a static [__set_state()](https://www.php.net/manual/en/language.oop5.magic.php#object.set-state) method that takes an associative array of property names to values, and returns a object. From 5aed7d8d4dc3725f6f94a06225e5376b26d55a31 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 18 Jun 2019 13:57:48 +0200 Subject: [PATCH 013/129] Note about anonymous classes --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 30248d3..6b5d60b 100644 --- a/README.md +++ b/README.md @@ -449,6 +449,8 @@ try { Feel free to open an issue or a pull request if you think that an internal class could/should be exportable. +- Exporting anonymous classes is not supported yet. Ideas or pull requests welcome. + - Just like `var_export()`, `VarExporter` cannot currently maintain object identity (two instances of the same object, once exported, will create two equal (`==`) yet distinct (`!==`) objects). - And just like `var_export()`, it cannot currently handle circular references, such as object `A` pointing to `B`, and `B` pointing back to `A`. From da09a7ce3bb3c984d275ad167359f73daee687da Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 21 Aug 2019 10:46:29 +0200 Subject: [PATCH 014/129] Add note about PHP 7.3 and __set_state() --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b5d60b..395e1ef 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ It is particularly useful to store data that can be cached by OPCache, just like But it also suffers from several drawbacks: -- It outputs invalid PHP code for `stdClass` objects, using `stdClass::__set_state()` which doesn't exist +- It outputs invalid PHP code for `stdClass` objects, using `stdClass::__set_state()` which doesn't exist (PHP < 7.3) - It cannot export custom objects that do not implement `__set_state()`, and `__set_state()` does not play well with private properties in parent classes, which makes the implementation tedious - It does not support closures From b5e3bbbf290b11b83af58563353b06aa28977c7b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 3 Dec 2019 12:50:03 +0100 Subject: [PATCH 015/129] PHP 7.4 is now GA --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6969344..1fc8d90 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,14 +4,12 @@ php: - 7.1 - 7.2 - 7.3 - - 7.4snapshot + - 7.4 matrix: include: - php: 7.1.0 env: dependencies=lowest - allow_failures: - - php: 7.4snapshot before_script: - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable; else composer update; fi; From f6cef92f01ab0b295df54e05411d6c48dd0ef079 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 24 Dec 2019 16:20:24 +0100 Subject: [PATCH 016/129] Object type hints, require PHP 7.2 --- .travis.yml | 3 +-- README.md | 2 +- composer.json | 2 +- src/Internal/GenericExporter.php | 2 +- src/Internal/ObjectExporter.php | 2 +- src/Internal/ObjectExporter/SetStateExporter.php | 4 ++-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1fc8d90..f3960f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,13 @@ language: php php: - - 7.1 - 7.2 - 7.3 - 7.4 matrix: include: - - php: 7.1.0 + - php: 7.2.0 env: dependencies=lowest before_script: diff --git a/README.md b/README.md index 395e1ef..715b645 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ composer require brick/varexporter ### Requirements -This library requires PHP 7.1 or later. +This library requires PHP 7.2 or later. ### Project status & release process diff --git a/composer.json b/composer.json index fe790c3..2da21f6 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "license": "MIT", "require": { - "php": ">=7.1", + "php": ">=7.2", "nikic/php-parser": "^4.0" }, "require-dev": { diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index dfaabcd..635c5d9 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -205,7 +205,7 @@ private function isScalarArray(array $array) : bool * * @throws ExportException */ - public function exportObject($object, array $path, array $parents) : array + public function exportObject(object $object, array $path, array $parents) : array { $hash = spl_object_hash($object); diff --git a/src/Internal/ObjectExporter.php b/src/Internal/ObjectExporter.php index 0f82234..9aa7293 100644 --- a/src/Internal/ObjectExporter.php +++ b/src/Internal/ObjectExporter.php @@ -47,7 +47,7 @@ abstract public function supports(\ReflectionObject $reflectionObject) : bool; * * @throws ExportException */ - abstract public function export($object, \ReflectionObject $reflectionObject, array $path, array $parents) : array; + abstract public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parents) : array; /** * Returns the code to create a new object of the given class. diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index a30950f..b075aba 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -62,7 +62,7 @@ public function export($object, \ReflectionObject $reflectionObject, array $path * * @throws ExportException */ - private function getObjectVars($object, array $path) : array + private function getObjectVars(object $object, array $path) : array { $result = []; @@ -99,7 +99,7 @@ private function getObjectVars($object, array $path) : array * * @return bool */ - private function isDynamicProperty($object, string $name) : bool + private function isDynamicProperty(object $object, string $name) : bool { $reflectionClass = new \ReflectionClass($object); $reflectionObject = new \ReflectionObject($object); From d94f083102137a559178201b4a56136e664ebab4 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 24 Dec 2019 16:21:04 +0100 Subject: [PATCH 017/129] Prepare for release --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 715b645..cb3651b 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ The current releases are numbered `0.x.y`. When a non-breaking change is introdu **When a breaking change is introduced, a new `0.x` version cycle is always started.** -It is therefore safe to lock your project to a given release cycle, such as `0.2.*`. +It is therefore safe to lock your project to a given release cycle, such as `0.3.*`. If you need to upgrade to a newer release cycle, check the [release history](https://github.com/brick/varexporter/releases) for a list of changes introduced by each further `0.x.0` version. From 358fdcadf518321ce85a03d1128c2734c6c950ed Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Wed, 22 Jan 2020 11:25:29 +0100 Subject: [PATCH 018/129] Supported exporting closures with use statements. Added `CLOSURE_SNAPSHOT_USE` option. --- README.md | 26 +++++- src/Internal/GenericExporter.php | 6 ++ .../ObjectExporter/ClosureExporter.php | 92 ++++++++++++++++--- src/VarExporter.php | 5 + tests/ExportClosureTest.php | 51 +++++++++- 5 files changed, 167 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index cb3651b..7e165f9 100644 --- a/README.md +++ b/README.md @@ -336,10 +336,34 @@ function (\My\App\Service $service) : \My\App\Model\Entity { Note how all namespaced classes, and explicitly namespaced functions and constants, have been rewritten, while the non-namespaced function `strlen()` and the non-namespaced constant have been left as is. This brings us to the first caveat: +## Use statements + +By default, exporting closures that have variables bound through `use()` will throw an `ExportException`. This is intentional, because exported closures can be executed in another context, and as such must not rely on the context they've been originally defined in. + +When using the `CLOSURE_SNAPSHOT_USE` option, `VarExporter` will export the current value of each `use()` variable instead of throwing an exception. The exported variables are added as expression inside the exported closure. + +```php +$planet = 'world'; + +echo VarExporter::export([ + 'callback' => function() use ($planet) { + return 'Hello, ' . $planet . '!'; + } +], VarExporter::CLOSURE_SNAPSHOT_USE); +``` + +```php +[ + 'callback' => function () { + $planet = 'world'; + return 'Hello, ' . $planet . '!'; + } +] +``` + ### Caveats - **Functions and constants that are not not explicitly namespaced**, either directly or through a `use function` or `use const` statement, **are always exported as is**. This is because the parser does not have the runtime context to check if a definition for this function or constant exists in the current namespace, and as such cannot reliably predict the behaviour of PHP's [fallback to global function/constant](https://www.php.net/manual/en/language.namespaces.fallback.php). Be really careful here if you're using namespaced functions or constants: **always explicitly import your namespaced functions and constants**, if any. -- **Closures that have variables bound through `use()` cannot be exported**, and will throw an `ExportException`. This is intentional, because exported closures can be executed in another context, and as such must not rely on the context they've been originally defined in. - Closures can use `$this`, but **will not be bound to an object once exported**. You must explicitly bind them through [`bindTo()`](https://www.php.net/manual/en/closure.bindto.php) if required, after running the exported code. - **You cannot have 2 closures on the same line in your source file**, or an `ExportException` will be thrown. This is because `VarExporter` cannot know which one holds the definition for the `\Closure` object it encountered. - **Closures defined in eval()'d code cannot be exported** and throw an `ExportException`, because there is no source file to parse. diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 635c5d9..3aa1fc2 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -48,6 +48,11 @@ final class GenericExporter */ public $inlineNumericScalarArray; + /** + * @var bool + */ + public $closureSnapshotUses; + /** * @param int $options */ @@ -76,6 +81,7 @@ public function __construct(int $options) $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); $this->inlineNumericScalarArray = (bool) ($options & VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); + $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); } /** diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 8adf3e2..27b00db 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -12,7 +12,9 @@ use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\FindingVisitor; use PhpParser\NodeVisitor\NameResolver; +use PhpParser\Parser; use PhpParser\ParserFactory; +use ReflectionFunction; /** * Handles closures. @@ -21,6 +23,11 @@ */ class ClosureExporter extends ObjectExporter { + /** + * @var Parser|null + */ + private $parser; + /** * {@inheritDoc} */ @@ -42,7 +49,7 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $ast = $this->parseFile($file, $path); $ast = $this->resolveNames($ast); - $closure = $this->getClosure($ast, $file, $line, $path); + $closure = $this->getClosure($reflectionFunction, $ast, $file, $line, $path); $prettyPrinter = new ClosureExporter\PrettyPrinter(); $prettyPrinter->setVarExporterNestingLevel(count($path)); @@ -55,6 +62,18 @@ public function export($object, \ReflectionObject $reflectionObject, array $path return [$code]; } + /** + * @return Parser + */ + private function getParser() + { + if ($this->parser === null) { + $this->parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); + } + + return $this->parser; + } + /** * Parses the given source file. * @@ -79,10 +98,8 @@ private function parseFile(string $filename, array $path) : array // @codeCoverageIgnoreEnd } - $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); - try { - return $parser->parse($source); + return $this->getParser()->parse($source); // @codeCoverageIgnoreStart } catch (Error $e) { throw new ExportException("Cannot parse file \"$filename\" for reading closure code.", $path, $e); @@ -109,17 +126,23 @@ private function resolveNames(array $ast) : array /** * Finds a closure in the source file and returns its node. * - * @param array $ast The AST. - * @param string $file The file name. - * @param int $line The line number where the closure is located in the source file. - * @param string[] $path The path to the closure in the array/object graph. + * @param ReflectionFunction $reflectionFunction Reflection of the closure. + * @param array $ast The AST. + * @param string $file The file name. + * @param int $line The line number where the closure is located in the source file. + * @param string[] $path The path to the closure in the array/object graph. * * @return Node\Expr\Closure * * @throws ExportException */ - private function getClosure(array $ast, string $file, int $line, array $path) : Node\Expr\Closure - { + private function getClosure( + ReflectionFunction $reflectionFunction, + array $ast, + string $file, + int $line, + array $path + ) : Node\Expr\Closure { $finder = new FindingVisitor(function(Node $node) use ($line) : bool { return $node instanceof Node\Expr\Closure && $node->getStartLine() === $line; @@ -145,9 +168,56 @@ private function getClosure(array $ast, string $file, int $line, array $path) : $closure = $closures[0]; if ($closure->uses) { - throw new ExportException("The closure has bound variables through 'use', this is not supported.", $path); + $this->closureHandleUses($reflectionFunction, $closure, $path); } return $closure; } + + /** + * Handle `use` part of closure. + * + * @param ReflectionFunction $reflectionFunction Reflection of the closure. + * @param Node\Expr\Closure $closure Parsed closure. + * @param string[] $path The path to the closure in the array/object graph. + * + * @throws ExportException + */ + private function closureHandleUses( + ReflectionFunction $reflectionFunction, + Node\Expr\Closure $closure, + array $path + ) : void { + if (! $this->exporter->closureSnapshotUses) { + throw new ExportException( + "The closure has bound variables through 'use', this is not supported by default. " . + "Use the `CLOSURE_SNAPSHOT_USE` option to export them.", + $path + ); + } + + $static = $reflectionFunction->getStaticVariables(); + $stmts = []; + + $parser = $this->getParser(); + + foreach ($closure->uses as $use) { + $var = $use->var->name; + + $export = array_merge(['exporter->export($static[$var], $path, []), [';']); + $nodes = $parser->parse(implode(PHP_EOL, $export)); + + /** @var Node\Stmt\Expression $expr */ + $expr = $nodes[0]; + + $assign = new Node\Expr\Assign( + new Node\Expr\Variable($var), + $expr->expr + ); + $stmts[] = new Node\Stmt\Expression($assign); + } + + $closure->uses = []; + $closure->stmts = array_merge($stmts, $closure->stmts); + } } diff --git a/src/VarExporter.php b/src/VarExporter.php index 7431abc..8f6e2b9 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -53,6 +53,11 @@ final class VarExporter */ public const INLINE_NUMERIC_SCALAR_ARRAY = 1 << 7; + /** + * Export static vars defined via `use` as variables. + */ + public const CLOSURE_SNAPSHOT_USES = 1 << 8; + /** * @param mixed $var The variable to export. * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index d78ca3c..63a3a7a 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -151,7 +151,56 @@ public function testExportClosureWithUse() return $foo; }; - $this->assertExportThrows("The closure has bound variables through 'use', this is not supported.", $var); + $this->assertExportThrows( + "The closure has bound variables through 'use', this is not supported by default. " . + "Use the `CLOSURE_SNAPSHOT_USE` option to export them.", + $var + ); + } + + public function testExportClosureWithUseAsVars() + { + $foo = 'b' . 'a' . 'r'; + + $var = function() use ($foo) { + return $foo; + }; + + $expected = <<<'PHP' +return function () { + $foo = 'bar'; + return $foo; +}; + +PHP; + + $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); + } + + public function testExportClosureWithUseClosure() + { + $foo = 'b' . 'a' . 'r'; + + $sub = function () use ($foo) { + return $foo; + }; + + $var = function() use ($sub) { + return $sub(); + }; + + $expected = <<<'PHP' +return function () { + $sub = function () { + $foo = 'bar'; + return $foo; + }; + return $sub(); +}; + +PHP; + + $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); } public function testExportClosureDefinedInEval() From c10011267a0b4871ef2a2aa7be720cb0bf8b23c0 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 23 Jan 2020 12:29:25 +0100 Subject: [PATCH 019/129] CS fixes --- src/Internal/GenericExporter.php | 2 +- .../ObjectExporter/ClosureExporter.php | 18 +++++++++--------- tests/ExportClosureTest.php | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 3aa1fc2..6cd127b 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -81,7 +81,7 @@ public function __construct(int $options) $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); $this->inlineNumericScalarArray = (bool) ($options & VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); - $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); + $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); } /** diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 27b00db..0b6f17f 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -126,11 +126,11 @@ private function resolveNames(array $ast) : array /** * Finds a closure in the source file and returns its node. * - * @param ReflectionFunction $reflectionFunction Reflection of the closure. - * @param array $ast The AST. - * @param string $file The file name. - * @param int $line The line number where the closure is located in the source file. - * @param string[] $path The path to the closure in the array/object graph. + * @param ReflectionFunction $reflectionFunction Reflection of the closure. + * @param array $ast The AST. + * @param string $file The file name. + * @param int $line The line number where the closure is located in the source file. + * @param string[] $path The path to the closure in the array/object graph. * * @return Node\Expr\Closure * @@ -177,9 +177,9 @@ private function getClosure( /** * Handle `use` part of closure. * - * @param ReflectionFunction $reflectionFunction Reflection of the closure. - * @param Node\Expr\Closure $closure Parsed closure. - * @param string[] $path The path to the closure in the array/object graph. + * @param ReflectionFunction $reflectionFunction Reflection of the closure. + * @param Node\Expr\Closure $closure Parsed closure. + * @param string[] $path The path to the closure in the array/object graph. * * @throws ExportException */ @@ -191,7 +191,7 @@ private function closureHandleUses( if (! $this->exporter->closureSnapshotUses) { throw new ExportException( "The closure has bound variables through 'use', this is not supported by default. " . - "Use the `CLOSURE_SNAPSHOT_USE` option to export them.", + "Use the CLOSURE_SNAPSHOT_USE option to export them.", $path ); } diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index 63a3a7a..20d96f6 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -153,7 +153,7 @@ public function testExportClosureWithUse() $this->assertExportThrows( "The closure has bound variables through 'use', this is not supported by default. " . - "Use the `CLOSURE_SNAPSHOT_USE` option to export them.", + "Use the CLOSURE_SNAPSHOT_USE option to export them.", $var ); } From e318e41ed05ad40a7c88efcfa77fc30391c43dd5 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 23 Jan 2020 12:31:24 +0100 Subject: [PATCH 020/129] Remove unnecessary space --- src/Internal/ObjectExporter/ClosureExporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 0b6f17f..fdac3bf 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -204,7 +204,7 @@ private function closureHandleUses( foreach ($closure->uses as $use) { $var = $use->var->name; - $export = array_merge(['exporter->export($static[$var], $path, []), [';']); + $export = array_merge(['exporter->export($static[$var], $path, []), [';']); $nodes = $parser->parse(implode(PHP_EOL, $export)); /** @var Node\Stmt\Expression $expr */ From 6ed295c1101c3e49cac91bad5f3e99070d366ce3 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 23 Jan 2020 12:34:27 +0100 Subject: [PATCH 021/129] Test against PHP 8 (nightly) --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f3960f3..d4a8c8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,18 @@ language: php +os: linux php: - 7.2 - 7.3 - 7.4 + - nightly -matrix: +jobs: include: - php: 7.2.0 env: dependencies=lowest + allow_failures: + - php: nightly before_script: - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable; else composer update; fi; From b3f32a3e2963fb5fc15dfea162625917b3f9b89c Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 23 Jan 2020 12:39:36 +0100 Subject: [PATCH 022/129] Fix composer install on nightly --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d4a8c8d..2844bf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ jobs: - php: nightly before_script: - - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable; else composer update; fi; + - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable; else composer update --ignore-platform-reqs; fi; script: - mkdir -p build/logs From d0da2310821834d23923d148104b486883382a53 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 23 Jan 2020 21:52:37 +0100 Subject: [PATCH 023/129] Support for arrow functions (#8) * Support exporting arrow functions. Arrow functions (aka short closures) are converted to regular closures. --- README.md | 31 ++++++++-- .../ObjectExporter/ClosureExporter.php | 50 ++++++++++++--- tests/ExportClosureTest.php | 61 +++++++++++++++++++ 3 files changed, 131 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7e165f9..1b3284a 100644 --- a/README.md +++ b/README.md @@ -346,17 +346,40 @@ When using the `CLOSURE_SNAPSHOT_USE` option, `VarExporter` will export the curr $planet = 'world'; echo VarExporter::export([ - 'callback' => function() use ($planet) { - return 'Hello, ' . $planet . '!'; + 'callback' => function(string $greeting) use ($planet) { + return $greeting . ', ' . $planet . '!'; } ], VarExporter::CLOSURE_SNAPSHOT_USE); ``` ```php [ - 'callback' => function () { + 'callback' => function (string $greeting) { + $planet = 'world'; + return $greeting . ', ' . $planet . '!'; + } +] +``` + +### Arrow functions + +PHP supports shorthand syntax for closures (since PHP 7.4), also known as arrow functions. `VarExporter` will export these as normal closures. + +Arrow functions can implicitly use variables from the context they've been defined in. If any context variable is used in the arrow function, `VarExporter` will throw an `ExportException` unless the `CLOSURE_SNAPSHOT_USES` option is used. + +```php +$planet = 'world'; + +echo VarExporter::export([ + 'callback' => fn(string $greeting) => $greeting . ', ' . $planet . '!'; +], VarExported::CLOSURE_SNAPSHOT_USES); +``` + +```php +[ + 'callback' => function (string $greeting) { $planet = 'world'; - return 'Hello, ' . $planet . '!'; + return $greeting . ', ' . $planet . '!'; } ] ``` diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index fdac3bf..a2d617a 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -144,7 +144,7 @@ private function getClosure( array $path ) : Node\Expr\Closure { $finder = new FindingVisitor(function(Node $node) use ($line) : bool { - return $node instanceof Node\Expr\Closure + return ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) && $node->getStartLine() === $line; }); @@ -164,9 +164,13 @@ private function getClosure( ), $path); } - /** @var Node\Expr\Closure $closure */ + /** @var Node\Expr\Closure|Node\Expr\ArrowFunction $closure */ $closure = $closures[0]; + if ($closure instanceof Node\Expr\ArrowFunction) { + $closure = $this->convertArrowFunction($reflectionFunction, $closure); + } + if ($closure->uses) { $this->closureHandleUses($reflectionFunction, $closure, $path); } @@ -174,6 +178,38 @@ private function getClosure( return $closure; } + /** + * Convert a parsed arrow function to a closure. + * + * @param ReflectionFunction $reflectionFunction Reflection of the closure. + * @param Node\Expr\ArrowFunction $arrowFunction Parsed arrow function. + * @param string[] $path The path to the closure in the array/object graph. + * + * @return Node\Expr\Closure + */ + private function convertArrowFunction( + ReflectionFunction $reflectionFunction, + Node\Expr\ArrowFunction $arrowFunction + ) : Node\Expr\Closure { + $closure = new Node\Expr\Closure([], ['arrow_function' => true]); + + $closure->static = false; + $closure->params = $arrowFunction->params; + $closure->returnType = $arrowFunction->returnType; + + $closure->stmts[] = new Node\Stmt\Return_($arrowFunction->expr); + + $static = $reflectionFunction->getStaticVariables(); + + foreach (array_keys($static) as $var) { + $closure->uses[] = new Node\Expr\ClosureUse( + new Node\Expr\Variable($var) + ); + } + + return $closure; + } + /** * Handle `use` part of closure. * @@ -189,11 +225,11 @@ private function closureHandleUses( array $path ) : void { if (! $this->exporter->closureSnapshotUses) { - throw new ExportException( - "The closure has bound variables through 'use', this is not supported by default. " . - "Use the CLOSURE_SNAPSHOT_USE option to export them.", - $path - ); + $message = $closure->hasAttribute('arrow_function') + ? "The arrow function uses variables in the parent scope, this is not supported by default" + : "The closure has bound variables through 'use', this is not supported by default"; + + throw new ExportException("$message. Use the CLOSURE_SNAPSHOT_USE option to export them.", $path); } $static = $reflectionFunction->getStaticVariables(); diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index 20d96f6..becc5b3 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -198,6 +198,67 @@ public function testExportClosureWithUseClosure() return $sub(); }; +PHP; + + $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); + } + + + public function testExportArrowFunction() + { + if (version_compare(PHP_VERSION, '7.4.0') < 0) { + $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); + } + + $var = [fn ($planet) => 'hello ' . $planet]; // Wrapping in array for valid syntax PHP <7.4 + + $expected = <<<'PHP' +return [ + function ($planet) { + return 'hello ' . $planet; + } +]; + +PHP; + + $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN); + } + + public function testExportArrowFunctionWithContext() + { + if (version_compare(PHP_VERSION, '7.4.0') < 0) { + $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); + } + + $greet = 'hello'; + + $var = [fn ($planet) => $greet . ' ' . $planet]; + + $this->assertExportThrows( + "The arrow function uses variables in the parent scope, this is not supported by default. " . + "Use the CLOSURE_SNAPSHOT_USE option to export them.", + $var + ); + } + + public function testExportArrowFunctionWithContextVarAsVar() + { + if (version_compare(PHP_VERSION, '7.4.0') < 0) { + $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); + } + + $greet = 'hello'; + + $var = [fn ($planet) => $greet . ' ' . $planet]; + + $expected = <<<'PHP' +return [ + function ($planet) { + $greet = 'hello'; + return $greet . ' ' . $planet; + } +]; + PHP; $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); From d3175908a49c40ddb890529c1e9a930728172cf7 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 23 Jan 2020 21:57:12 +0100 Subject: [PATCH 024/129] Fix title [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b3284a..507596f 100644 --- a/README.md +++ b/README.md @@ -336,7 +336,7 @@ function (\My\App\Service $service) : \My\App\Model\Entity { Note how all namespaced classes, and explicitly namespaced functions and constants, have been rewritten, while the non-namespaced function `strlen()` and the non-namespaced constant have been left as is. This brings us to the first caveat: -## Use statements +### Use statements By default, exporting closures that have variables bound through `use()` will throw an `ExportException`. This is intentional, because exported closures can be executed in another context, and as such must not rely on the context they've been originally defined in. From 4b8b4162961bb186afaaf5cf5158f730d6564959 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 23 Jan 2020 22:28:44 +0100 Subject: [PATCH 025/129] Replace spl_object_hash() with spl_object_id() --- src/Internal/GenericExporter.php | 47 +++++++++---------- src/Internal/ObjectExporter.php | 4 +- .../ObjectExporter/AnyObjectExporter.php | 14 +++--- .../ObjectExporter/ClosureExporter.php | 2 +- .../ObjectExporter/InternalClassExporter.php | 2 +- .../ObjectExporter/SerializeExporter.php | 4 +- .../ObjectExporter/SetStateExporter.php | 4 +- .../ObjectExporter/StdClassExporter.php | 4 +- 8 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 6cd127b..766e8bf 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -26,10 +26,9 @@ final class GenericExporter /** * The visited objects, to detect circular references. * - * This is a two-level map of parent object hash => child object hash => path where the object first appeared. - * [string => [string => string[]]] + * This is a two-level map of parent object id => child object id => path where the object first appeared. * - * @var array + * @var array> */ private $visitedObjects = []; @@ -85,15 +84,15 @@ public function __construct(int $options) } /** - * @param mixed $var The variable to export. - * @param string[] $path The path to the current variable in the array/object graph. - * @param string[] $parents The hashes of all objects higher in the graph. + * @param mixed $var The variable to export. + * @param string[] $path The path to the current variable in the array/object graph. + * @param int[] $parentIds The ids of all objects higher in the graph. * * @return string[] The lines of code. * * @throws ExportException */ - public function export($var, array $path, array $parents) : array + public function export($var, array $path, array $parentIds) : array { switch ($type = gettype($var)) { case 'boolean': @@ -107,10 +106,10 @@ public function export($var, array $path, array $parents) : array return ['null']; case 'array': - return $this->exportArray($var, $path, $parents); + return $this->exportArray($var, $path, $parentIds); case 'object': - return $this->exportObject($var, $path, $parents); + return $this->exportObject($var, $path, $parentIds); default: // resources @@ -119,15 +118,15 @@ public function export($var, array $path, array $parents) : array } /** - * @param array $array The array to export. - * @param string[] $path The path to the current array in the array/object graph. - * @param string[] $parents The hashes of all objects higher in the graph. + * @param array $array The array to export. + * @param string[] $path The path to the current array in the array/object graph. + * @param int[] $parentIds The ids of all objects higher in the graph. * * @return string[] The lines of code. * * @throws ExportException */ - public function exportArray(array $array, array $path, array $parents) : array + public function exportArray(array $array, array $path, array $parentIds) : array { if (! $array) { return ['[]']; @@ -148,7 +147,7 @@ public function exportArray(array $array, array $path, array $parents) : array $newPath = $path; $newPath[] = (string) $key; - $exported = $this->export($value, $newPath, $parents); + $exported = $this->export($value, $newPath, $parentIds); if ($inline) { $result[] = $exported[0]; @@ -203,36 +202,36 @@ private function isScalarArray(array $array) : bool } /** - * @param object $object The object to export. - * @param string[] $path The path to the current object in the array/object graph. - * @param string[] $parents The hashes of all objects higher in the graph. + * @param object $object The object to export. + * @param string[] $path The path to the current object in the array/object graph. + * @param int[] $parentIds The ids of all objects higher in the graph. * * @return string[] The lines of code. * * @throws ExportException */ - public function exportObject(object $object, array $path, array $parents) : array + public function exportObject(object $object, array $path, array $parentIds) : array { - $hash = spl_object_hash($object); + $id = spl_object_id($object); - foreach ($parents as $parentHash) { - if (isset($this->visitedObjects[$parentHash][$hash])) { + foreach ($parentIds as $parentId) { + if (isset($this->visitedObjects[$parentId][$id])) { throw new ExportException(sprintf( 'Object of class "%s" has a circular reference at %s. ' . 'Circular references are currently not supported.', get_class($object), - ExportException::pathToString($this->visitedObjects[$parentHash][$hash]) + ExportException::pathToString($this->visitedObjects[$parentId][$id]) ), $path); } - $this->visitedObjects[$parentHash][$hash] = $path; + $this->visitedObjects[$parentId][$id] = $path; } $reflectionObject = new \ReflectionObject($object); foreach ($this->objectExporters as $objectExporter) { if ($objectExporter->supports($reflectionObject)) { - return $objectExporter->export($object, $reflectionObject, $path, $parents); + return $objectExporter->export($object, $reflectionObject, $path, $parentIds); } } diff --git a/src/Internal/ObjectExporter.php b/src/Internal/ObjectExporter.php index 9aa7293..9b34614 100644 --- a/src/Internal/ObjectExporter.php +++ b/src/Internal/ObjectExporter.php @@ -41,13 +41,13 @@ abstract public function supports(\ReflectionObject $reflectionObject) : bool; * @param object $object The object to export. * @param \ReflectionObject $reflectionObject A reflection of the object. * @param string[] $path The path to the current object in the array/object graph. - * @param string[] $parents The hashes of all objects higher in the graph. + * @param int[] $parentIds The ids of all objects higher in the graph. * * @return string[] The lines of code. * * @throws ExportException */ - abstract public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parents) : array; + abstract public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array; /** * Returns the code to create a new object of the given class. diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index 61569a1..c93c428 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -27,7 +27,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parents) : array + public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $lines = $this->getCreateObjectCode($reflectionObject); @@ -91,10 +91,10 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $newPath = $path; $newPath[] = $name; - $newParents = $parents; - $newParents[] = spl_object_hash($object); + $newParentIds = $parentIds; + $newParentIds[] = spl_object_id($object); - $exportedValue = $this->exporter->export($value, $newPath, $newParents); + $exportedValue = $this->exporter->export($value, $newPath, $newParentIds); $exportedValue = $this->exporter->wrap($exportedValue, '$object->' . $this->escapePropName($name) . ' = ', ';'); $lines = array_merge($lines, $exportedValue); } @@ -115,10 +115,10 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $newPath = $path; $newPath[] = $name; - $newParents = $parents; - $newParents[] = spl_object_hash($object); + $newParentIds = $parentIds; + $newParentIds[] = spl_object_id($object); - $exportedValue = $this->exporter->export($value, $newPath, $newParents); + $exportedValue = $this->exporter->export($value, $newPath, $newParentIds); $exportedValue = $this->exporter->wrap($exportedValue, '$this->' . $this->escapePropName($name) . ' = ', ';'); $closureLines = array_merge($closureLines, $exportedValue); } diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index a2d617a..26b4ef0 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -39,7 +39,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parents) : array + public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $reflectionFunction = new \ReflectionFunction($object); diff --git a/src/Internal/ObjectExporter/InternalClassExporter.php b/src/Internal/ObjectExporter/InternalClassExporter.php index f962c2f..1deb23e 100644 --- a/src/Internal/ObjectExporter/InternalClassExporter.php +++ b/src/Internal/ObjectExporter/InternalClassExporter.php @@ -25,7 +25,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parents) : array + public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $className = $reflectionObject->getName(); diff --git a/src/Internal/ObjectExporter/SerializeExporter.php b/src/Internal/ObjectExporter/SerializeExporter.php index ee774ff..c02fd58 100644 --- a/src/Internal/ObjectExporter/SerializeExporter.php +++ b/src/Internal/ObjectExporter/SerializeExporter.php @@ -25,7 +25,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parents) : array + public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $lines = $this->getCreateObjectCode($reflectionObject); @@ -33,7 +33,7 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $values = $object->__serialize(); - $exportedValues = $this->exporter->export($values, $path, $parents); + $exportedValues = $this->exporter->export($values, $path, $parentIds); $exportedValues = $this->exporter->wrap($exportedValues, '$object->__unserialize(', ');'); $lines = array_merge($lines, $exportedValues); diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index b075aba..85026e2 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -31,13 +31,13 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parents) : array + public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $className = $reflectionObject->getName(); $vars = $this->getObjectVars($object, $path); - $exportedVars = $this->exporter->exportArray($vars, $path, $parents); + $exportedVars = $this->exporter->exportArray($vars, $path, $parentIds); $exportedVars = $this->exporter->wrap($exportedVars, '\\' . $className . '::__set_state(', ')'); return $exportedVars; diff --git a/src/Internal/ObjectExporter/StdClassExporter.php b/src/Internal/ObjectExporter/StdClassExporter.php index 81e1857..52fc0a9 100644 --- a/src/Internal/ObjectExporter/StdClassExporter.php +++ b/src/Internal/ObjectExporter/StdClassExporter.php @@ -24,9 +24,9 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parents) : array + public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { - $exported = $this->exporter->exportArray((array) $object, $path, $parents); + $exported = $this->exporter->exportArray((array) $object, $path, $parentIds); $exported[0] = '(object) ' . $exported[0]; From 7150917a8625c7fc4026de33c5b7718b48ed5a94 Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Fri, 24 Jan 2020 10:23:08 +0100 Subject: [PATCH 026/129] Added VarExporter::CLOSURE_SNAPSHOT_USES to README --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 507596f..02c77e7 100644 --- a/README.md +++ b/README.md @@ -340,7 +340,7 @@ Note how all namespaced classes, and explicitly namespaced functions and constan By default, exporting closures that have variables bound through `use()` will throw an `ExportException`. This is intentional, because exported closures can be executed in another context, and as such must not rely on the context they've been originally defined in. -When using the `CLOSURE_SNAPSHOT_USE` option, `VarExporter` will export the current value of each `use()` variable instead of throwing an exception. The exported variables are added as expression inside the exported closure. +When using the [`CLOSURE_SNAPSHOT_USES`](#varexporterclosure_snapshot_uses) option, `VarExporter` will export the current value of each `use()` variable instead of throwing an exception. The exported variables are added as expression inside the exported closure. ```php $planet = 'world'; @@ -365,7 +365,7 @@ echo VarExporter::export([ PHP supports shorthand syntax for closures (since PHP 7.4), also known as arrow functions. `VarExporter` will export these as normal closures. -Arrow functions can implicitly use variables from the context they've been defined in. If any context variable is used in the arrow function, `VarExporter` will throw an `ExportException` unless the `CLOSURE_SNAPSHOT_USES` option is used. +Arrow functions can implicitly use variables from the context they've been defined in. If any context variable is used in the arrow function, `VarExporter` will throw an `ExportException` unless the [`CLOSURE_SNAPSHOT_USES`](#varexporterclosure_snapshot_uses) option is used. ```php $planet = 'world'; @@ -473,6 +473,10 @@ VarExporter::export([ Types considered scalar here are `int`, `bool`, `float`, `string` and `null`. +### `VarExporter::CLOSURE_SNAPSHOT_USES` + +Export the current value of each `use()` variable as expression inside the exported closure. + ## Error handling Any error occurring on `export()` will throw an `ExportException`: From 59fd646de005c52222279618251eb476cb3bf80f Mon Sep 17 00:00:00 2001 From: GameplayJDK Date: Fri, 13 Mar 2020 17:56:53 +0100 Subject: [PATCH 027/129] Support for internal classes providing __set_state() implementation (#10) (#11) --- README.md | 2 +- src/Internal/GenericExporter.php | 4 ++-- tests/VarExporterTest.php | 36 ++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 02c77e7..635d59e 100644 --- a/README.md +++ b/README.md @@ -494,7 +494,7 @@ try { ## Limitations -- Exporting internal classes other than `stdClass` and `Closure` is currently not supported. `VarExporter` will throw an `ExportException` if it finds one. +- Exporting internal classes other than `stdClass` and `Closure`, and classes implementing `__set_state()` (most notably DateTime classes) is currently not supported. `VarExporter` will throw an `ExportException` if it finds one. To avoid hitting this brick wall, you can implement `__serialize()` and `__unserialize()` in classes that contain references to internal objects. diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 766e8bf..f1e8923 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -63,12 +63,12 @@ public function __construct(int $options) $this->objectExporters[] = new ObjectExporter\ClosureExporter($this); } - $this->objectExporters[] = new ObjectExporter\InternalClassExporter($this); - if (! ($options & VarExporter::NO_SET_STATE)) { $this->objectExporters[] = new ObjectExporter\SetStateExporter($this); } + $this->objectExporters[] = new ObjectExporter\InternalClassExporter($this); + if (! ($options & VarExporter::NO_SERIALIZE)) { $this->objectExporters[] = new ObjectExporter\SerializeExporter($this); } diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index c4bbf58..381a627 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -151,6 +151,42 @@ public function testInlineNumericScalarArray() $this->assertExportEquals($expected, $var, VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); } + public function testExportDateTime() + { + $timezone = new \DateTimeZone('Europe/Berlin'); + $format = 'Y-m-d H:i:s.u'; + + $var = \DateTime::createFromFormat($format, '2020-03-09 18:51:23.000000', $timezone); + + $expected = <<<'PHP' +\DateTime::__set_state([ + 'date' => '2020-03-09 18:51:23.000000', + 'timezone_type' => 3, + 'timezone' => 'Europe/Berlin' +]) +PHP; + + $this->assertExportEquals($expected, $var); + } + + public function testExportDateTimeImmutable() + { + $timezone = new \DateTimeZone('Europe/Berlin'); + $format = 'Y-m-d H:i:s.u'; + + $var = \DateTimeImmutable::createFromFormat($format, '2020-03-10 17:06:19.000000', $timezone); + + $expected = <<<'PHP' +\DateTimeImmutable::__set_state([ + 'date' => '2020-03-10 17:06:19.000000', + 'timezone_type' => 3, + 'timezone' => 'Europe/Berlin' +]) +PHP; + + $this->assertExportEquals($expected, $var); + } + public function testExportInternalClass() { $object = new \stdClass; From ec758c92d1b5c8e2b140c8bcda808fb089ff2b12 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 20 Mar 2020 19:24:19 +0100 Subject: [PATCH 028/129] Upgrade to PHPUnit 8 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2da21f6..b2ad315 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "nikic/php-parser": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^8.0", "php-coveralls/php-coveralls": "^2.0" }, "autoload": { From 6d84889280e70a9923cc2040d8c046477e8dff07 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 6 Apr 2020 11:50:04 +0100 Subject: [PATCH 029/129] Update composer.json (#12) --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index b2ad315..6bfad6d 100644 --- a/composer.json +++ b/composer.json @@ -7,12 +7,12 @@ ], "license": "MIT", "require": { - "php": ">=7.2", + "php": "^7.2|^8.0", "nikic/php-parser": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.0", - "php-coveralls/php-coveralls": "^2.0" + "phpunit/phpunit": "^8.1", + "php-coveralls/php-coveralls": "^2.2" }, "autoload": { "psr-4": { From cccc53ec32cee4c389511f3974188537f5dcfcbc Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Mon, 4 May 2020 17:54:20 +0200 Subject: [PATCH 030/129] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 635d59e..93f2c63 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ Additionally, the output is not very pretty: - It outputs arrays as `array()` notation, instead of the short `[]` notation - It outputs numeric arrays with explicit and unnecessary `0 => ...` key => value syntax -This library aims to provide a prettier, safer, and powerful alternative to `var_export()`. +This library aims to provide a prettier, safer, and powerful alternative to `var_export()`. +The output is **valid and standalone PHP code, that does not depend on the `brick/varexporter` library**. ### Installation From b98d89478b550fc20a0ebe150edf8a24d6459b53 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 21 Aug 2020 14:28:42 +0200 Subject: [PATCH 031/129] Update Coveralls badge [ci skip] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93f2c63..2326380 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A powerful and pretty replacement for PHP's `var_export()`. [![Build Status](https://secure.travis-ci.org/brick/varexporter.svg?branch=master)](http://travis-ci.org/brick/varexporter) -[![Coverage Status](https://coveralls.io/repos/brick/varexporter/badge.svg?branch=master)](https://coveralls.io/r/brick/varexporter?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/brick/varexporter/badge.svg?branch=master)](https://coveralls.io/github/brick/varexporter?branch=master) [![Latest Stable Version](https://poser.pugx.org/brick/varexporter/v/stable)](https://packagist.org/packages/brick/varexporter) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) From 17e43b0c13a06dfd03b89e7c64ba7fcdb7e57b98 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 15 Oct 2020 00:05:13 +0200 Subject: [PATCH 032/129] Remove .idea from .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 99d85f6..32df3db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /vendor /composer.lock /.phpunit.result.cache -/.idea + From 6fbfd9346feb34260aac90df9056dcb269b7a75c Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 6 Dec 2020 00:39:21 +0100 Subject: [PATCH 033/129] Travis on PHP 8 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2844bf6..9dfab2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ php: - 7.2 - 7.3 - 7.4 - - nightly + - 8.0 jobs: include: From 5e70b5c04c294c33c7a3a69557c8786920424732 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 6 Dec 2020 00:46:17 +0100 Subject: [PATCH 034/129] Travis on PHP 8 --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9dfab2c..dc1a022 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,6 @@ jobs: include: - php: 7.2.0 env: dependencies=lowest - allow_failures: - - php: nightly before_script: - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable; else composer update --ignore-platform-reqs; fi; From 3b4de481516e84865a4d97df8e1332eb4b4f1fd3 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 6 Dec 2020 01:23:42 +0100 Subject: [PATCH 035/129] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6bfad6d..5818fae 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "license": "MIT", "require": { - "php": "^7.2|^8.0", + "php": "^7.2 || ^8.0", "nikic/php-parser": "^4.0" }, "require-dev": { From 28ac47d9c4fd9bf52c7d03aa02a7032bfe8a72fd Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Mon, 14 Dec 2020 23:58:03 +0100 Subject: [PATCH 036/129] Upgrade PHPUnit --- composer.json | 2 +- phpunit.xml | 22 +++++++++++----------- tests/AbstractTestCase.php | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index 5818fae..fe95fe6 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "nikic/php-parser": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.1", + "phpunit/phpunit": "^8.1 || ^9.0", "php-coveralls/php-coveralls": "^2.2" }, "autoload": { diff --git a/phpunit.xml b/phpunit.xml index afd0ad8..d6cb2ff 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,13 @@ - - - - tests - - - - - src - - + + + + tests + + + + + src + + diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index f022610..edcef7a 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -68,7 +68,7 @@ public function assertExportThrows(string $expectedMessage, $var, int $options = }, explode('*', $expectedMessage))) . '/'; $this->expectException(ExportException::class); - $this->expectExceptionMessageRegExp($expectedMessageRegExp); + $this->expectExceptionMessageMatches($expectedMessageRegExp); VarExporter::export($var, $options); } From b0c5e631063f51c8464682aa6274b9b2f0025ec4 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 15 Dec 2020 00:01:43 +0100 Subject: [PATCH 037/129] Move CI to GitHub Actions --- .github/workflows/ci.yml | 56 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 23 ----------------- README.md | 2 +- 3 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c853a76 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + phpunit: + name: PHPUnit + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: + - "7.2" + - "7.3" + - "7.4" + - "8.0" + deps: + - "highest" + include: + - php-version: "7.2" + deps: "lowest" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: xdebug + + - name: Install composer dependencies + uses: "ramsey/composer-install@v1" + with: + dependency-versions: ${{ matrix.deps }} + + - name: Run PHPUnit + run: vendor/bin/phpunit + if: ${{ matrix.php-version != '8.0' }} + + - name: Run PHPUnit with coverage + run: | + mkdir -p mkdir -p build/logs + vendor/bin/phpunit --coverage-clover build/logs/clover.xml + if: ${{ matrix.php-version == '8.0' }} + + - name: Upload coverage report to Coveralls + run: vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: ${{ matrix.php-version == '8.0' }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index dc1a022..0000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: php -os: linux - -php: - - 7.2 - - 7.3 - - 7.4 - - 8.0 - -jobs: - include: - - php: 7.2.0 - env: dependencies=lowest - -before_script: - - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable; else composer update --ignore-platform-reqs; fi; - -script: - - mkdir -p build/logs - - vendor/bin/phpunit --coverage-clover build/logs/clover.xml - -after_script: - - vendor/bin/php-coveralls -v diff --git a/README.md b/README.md index 2326380..ec4b7a3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A powerful and pretty replacement for PHP's `var_export()`. -[![Build Status](https://secure.travis-ci.org/brick/varexporter.svg?branch=master)](http://travis-ci.org/brick/varexporter) +[![Build Status](https://github.com/brick/varexporter/workflows/CI/badge.svg)](https://github.com/brick/varexporter/actions) [![Coverage Status](https://coveralls.io/repos/github/brick/varexporter/badge.svg?branch=master)](https://coveralls.io/github/brick/varexporter?branch=master) [![Latest Stable Version](https://poser.pugx.org/brick/varexporter/v/stable)](https://packagist.org/packages/brick/varexporter) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) From 45969ff4311cc4cbb2473e57a936312a4ddbc0ee Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 15 Dec 2020 00:05:24 +0100 Subject: [PATCH 038/129] Fix minimum PHPUnit version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fe95fe6..3ae2f74 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "nikic/php-parser": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.1 || ^9.0", + "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2" }, "autoload": { From 18e2b607a24ab0852688b22c078072ce7269c9c9 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 24 Dec 2020 15:18:58 +0100 Subject: [PATCH 039/129] Fix exporting objects with dynamic numeric properties --- src/Internal/ObjectExporter/AnyObjectExporter.php | 2 ++ src/Internal/ObjectExporter/SetStateExporter.php | 1 + tests/ExportObjectTest.php | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index c93c428..b2ae255 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -88,6 +88,8 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $lines[] = ''; foreach ($publicProperties as $name => $value) { + $name = (string) $name; + $newPath = $path; $newPath[] = $name; diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index 85026e2..ca8b51a 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -67,6 +67,7 @@ private function getObjectVars(object $object, array $path) : array $result = []; foreach ((array) $object as $name => $value) { + $name = (string) $name; $pos = strrpos($name, "\0"); if ($pos !== false) { diff --git a/tests/ExportObjectTest.php b/tests/ExportObjectTest.php index fa6e93e..f487fb5 100644 --- a/tests/ExportObjectTest.php +++ b/tests/ExportObjectTest.php @@ -57,6 +57,7 @@ public function testExportClassWithDynamicPropertiesOnly() $object->x = 1.0; $object->dynamicProp = 'Hello'; $object->{'$weird%Prop'} = 'World'; + $object->{'123'} = 'Numeric dynamic prop'; $expected = <<<'PHP' (static function() { @@ -65,6 +66,7 @@ public function testExportClassWithDynamicPropertiesOnly() $object->x = 1.0; $object->dynamicProp = 'Hello'; $object->{'$weird%Prop'} = 'World'; + $object->{'123'} = 'Numeric dynamic prop'; return $object; })() @@ -203,13 +205,15 @@ public function testExportClassWithSetStateAndDynamicProperties() $object->foo = 'Hello'; $object->bar = 'World'; $object->dynamic = 'Dynamic property'; + $object->{'123'} = 'Numeric dynamic property'; $expected = <<<'PHP' \Brick\VarExporter\Tests\Classes\SetState::__set_state([ 'baz' => 'defaultValue', 'foo' => 'Hello', 'bar' => 'World', - 'dynamic' => 'Dynamic property' + 'dynamic' => 'Dynamic property', + 123 => 'Numeric dynamic property' ]) PHP; From 0c21e0fcf645e1b8578b4f4f57fe54464d3c38aa Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 24 Dec 2020 15:38:24 +0100 Subject: [PATCH 040/129] Psalm level 1 --- composer.json | 3 ++- psalm.xml | 15 ++++++++++++++ src/Internal/GenericExporter.php | 4 ++++ .../ObjectExporter/AnyObjectExporter.php | 2 ++ .../ObjectExporter/ClosureExporter.php | 20 ++++++++++++++++--- .../ClosureExporter/PrettyPrinter.php | 4 +++- .../ObjectExporter/SerializeExporter.php | 4 ++++ .../ObjectExporter/SetStateExporter.php | 10 +++++++--- 8 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 psalm.xml diff --git a/composer.json b/composer.json index 3ae2f74..cab60c4 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ }, "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", - "php-coveralls/php-coveralls": "^2.2" + "php-coveralls/php-coveralls": "^2.2", + "vimeo/psalm": "^4.3" }, "autoload": { "psr-4": { diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..3240886 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index f1e8923..48c0d8d 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -106,9 +106,11 @@ public function export($var, array $path, array $parentIds) : array return ['null']; case 'array': + /** @var array $var */ return $this->exportArray($var, $path, $parentIds); case 'object': + /** @var object $var */ return $this->exportObject($var, $path, $parentIds); default: @@ -118,6 +120,8 @@ public function export($var, array $path, array $parentIds) : array } /** + * @psalm-suppress MixedAssignment + * * @param array $array The array to export. * @param string[] $path The path to the current array in the array/object graph. * @param int[] $parentIds The ids of all objects higher in the graph. diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index b2ae255..69b3c7f 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -26,6 +26,8 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} + * + * @psalm-suppress MixedAssignment */ public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 26b4ef0..f2eb35c 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -6,7 +6,7 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; - +use Closure; use PhpParser\Error; use PhpParser\Node; use PhpParser\NodeTraverser; @@ -41,6 +41,8 @@ public function supports(\ReflectionObject $reflectionObject) : bool */ public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { + assert($object instanceof Closure); + $reflectionFunction = new \ReflectionFunction($object); $file = $reflectionFunction->getFileName(); @@ -99,7 +101,12 @@ private function parseFile(string $filename, array $path) : array } try { - return $this->getParser()->parse($source); + $nodes = $this->getParser()->parse($source); + + // throwing error handler + assert($nodes !== null); + + return $nodes; // @codeCoverageIgnoreStart } catch (Error $e) { throw new ExportException("Cannot parse file \"$filename\" for reading closure code.", $path, $e); @@ -127,7 +134,7 @@ private function resolveNames(array $ast) : array * Finds a closure in the source file and returns its node. * * @param ReflectionFunction $reflectionFunction Reflection of the closure. - * @param array $ast The AST. + * @param Node[] $ast The AST. * @param string $file The file name. * @param int $line The line number where the closure is located in the source file. * @param string[] $path The path to the closure in the array/object graph. @@ -202,6 +209,8 @@ private function convertArrowFunction( $static = $reflectionFunction->getStaticVariables(); foreach (array_keys($static) as $var) { + assert(is_string($var)); + $closure->uses[] = new Node\Expr\ClosureUse( new Node\Expr\Variable($var) ); @@ -240,9 +249,14 @@ private function closureHandleUses( foreach ($closure->uses as $use) { $var = $use->var->name; + assert(is_string($var)); + $export = array_merge(['exporter->export($static[$var], $path, []), [';']); $nodes = $parser->parse(implode(PHP_EOL, $export)); + // throwing error handler + assert($nodes !== null); + /** @var Node\Stmt\Expression $expr */ $expr = $nodes[0]; diff --git a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php index a899d09..4010810 100644 --- a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php +++ b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php @@ -8,6 +8,8 @@ /** * Extends the standard pretty-printer to allow for a base indent level. + * + * @psalm-suppress PropertyNotSetInConstructor */ final class PrettyPrinter extends Standard { @@ -29,7 +31,7 @@ public function setVarExporterNestingLevel(int $level) : void /** * {@inheritDoc} */ - protected function resetState() + protected function resetState() : void { parent::resetState(); diff --git a/src/Internal/ObjectExporter/SerializeExporter.php b/src/Internal/ObjectExporter/SerializeExporter.php index c02fd58..7c826c3 100644 --- a/src/Internal/ObjectExporter/SerializeExporter.php +++ b/src/Internal/ObjectExporter/SerializeExporter.php @@ -31,6 +31,10 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $lines[] = ''; + /** + * @psalm-suppress MixedAssignment + * @psalm-suppress MixedMethodCall + */ $values = $object->__serialize(); $exportedValues = $this->exporter->export($values, $path, $parentIds); diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index ca8b51a..4963dd2 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -55,10 +55,12 @@ public function export($object, \ReflectionObject $reflectionObject, array $path * * This way we offer a better safety guarantee, while staying compatible with var_export() in the output. * - * @param object $object The object to dump. - * @param array $path The path to the object, in the array/object graph. + * @psalm-suppress MixedAssignment * - * @return array An associative array of property name to value. + * @param object $object The object to dump. + * @param string[] $path The path to the object, in the array/object graph. + * + * @return array An associative array of property name to value. * * @throws ExportException */ @@ -74,6 +76,8 @@ private function getObjectVars(object $object, array $path) : array $name = substr($name, $pos + 1); } + assert($name !== false); + if (array_key_exists($name, $result)) { $className = get_class($object); From 85b4c40378d6019a2e642fcebd7ca00c488da9af Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 24 Dec 2020 15:40:05 +0100 Subject: [PATCH 041/129] Psalm on GitHub Actions --- .github/workflows/ci.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c853a76..217f771 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,25 @@ on: branches: [ master ] jobs: + psalm: + name: Psalm + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.0" + + - name: Install composer dependencies + uses: "ramsey/composer-install@v1" + + - name: Run Psalm + run: vendor/bin/psalm --show-info=false --find-unused-psalm-suppress --no-progress + phpunit: name: PHPUnit runs-on: ubuntu-latest From 62cf02e828e8f3872937395c84173621a3c9caec Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 24 Dec 2020 16:04:41 +0100 Subject: [PATCH 042/129] Fix Psalm issue --- src/Internal/ObjectExporter/AnyObjectExporter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index 69b3c7f..5449e71 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -90,6 +90,7 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $lines[] = ''; foreach ($publicProperties as $name => $value) { + /** @psalm-suppress RedundantCast See: https://github.com/vimeo/psalm/issues/4891 */ $name = (string) $name; $newPath = $path; From 831d948708bd7bd874c69d9c810297605c65f1c9 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 8 Jan 2021 23:02:48 +0100 Subject: [PATCH 043/129] Lock Psalm version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cab60c4..58e1988 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "^4.3" + "vimeo/psalm": "4.3.2" }, "autoload": { "psr-4": { From eaa86141b6cc20b1ef9c087453a5db7507e17991 Mon Sep 17 00:00:00 2001 From: ADmad Date: Mon, 8 Feb 2021 04:07:54 +0530 Subject: [PATCH 044/129] Add support for trailing comma in arrays. (#16) --- README.md | 25 +++++++++++++++++++++++++ src/Internal/GenericExporter.php | 8 +++++++- src/VarExporter.php | 6 ++++++ tests/VarExporterTest.php | 21 +++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ec4b7a3..906bc61 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,31 @@ VarExporter::export([ Types considered scalar here are `int`, `bool`, `float`, `string` and `null`. +### `VarExporter::TRAILING_COMMA_IN_ARRAY` + +Add trailing comma for last item of an array: + +```php +VarExporter::export([ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => ['hello', 'world', ['one', 'two', 'three']] +], VarExporter::TRAILING_COMMA_IN_ARRAY | VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); +``` + +```php +[ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => [ + 'hello', + 'world', + ['one', 'two', 'three'], + ], +] +``` + +Trailing comma won't be added to inlined arrays if this flag is used along with +`VarExporter::INLINE_NUMERIC_SCALAR_ARRAY`. + ### `VarExporter::CLOSURE_SNAPSHOT_USES` Export the current value of each `use()` variable as expression inside the exported closure. diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 48c0d8d..23629ad 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -52,6 +52,11 @@ final class GenericExporter */ public $closureSnapshotUses; + /** + * @var bool + */ + public $trailingCommaInArray; + /** * @param int $options */ @@ -81,6 +86,7 @@ public function __construct(int $options) $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); $this->inlineNumericScalarArray = (bool) ($options & VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); + $this->trailingCommaInArray = (bool) ($options & VarExporter::TRAILING_COMMA_IN_ARRAY); } /** @@ -163,7 +169,7 @@ public function exportArray(array $array, array $path, array $parentIds) : array $prepend = var_export($key, true) . ' => '; } - if (! $isLast) { + if (! $isLast || $this->trailingCommaInArray) { $append = ','; } diff --git a/src/VarExporter.php b/src/VarExporter.php index 8f6e2b9..0185554 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -58,6 +58,12 @@ final class VarExporter */ public const CLOSURE_SNAPSHOT_USES = 1 << 8; + /** + * Add trailing comma for last item of an array. + * Trailing comma won't be added to inlined arrays even if this flag is used. + */ + public const TRAILING_COMMA_IN_ARRAY = 1 << 9; + /** * @param mixed $var The variable to export. * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 381a627..7462d23 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -151,6 +151,27 @@ public function testInlineNumericScalarArray() $this->assertExportEquals($expected, $var, VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); } + public function testTrailingCommaInArray() + { + $var = [ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => ['hello', 'world', ['one', 'two', 'three']] + ]; + + $expected = <<<'PHP' +[ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => [ + 'hello', + 'world', + ['one', 'two', 'three'], + ], +] +PHP; + + $this->assertExportEquals($expected, $var, VarExporter::INLINE_NUMERIC_SCALAR_ARRAY | VarExporter::TRAILING_COMMA_IN_ARRAY); + } + public function testExportDateTime() { $timezone = new \DateTimeZone('Europe/Berlin'); From 9364f773344569e755a1c9e33e6047cd4446dc73 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 7 Feb 2021 23:50:07 +0100 Subject: [PATCH 045/129] Change wording --- README.md | 22 ++++++++-------------- src/VarExporter.php | 3 +-- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 906bc61..d67e0b1 100644 --- a/README.md +++ b/README.md @@ -476,29 +476,23 @@ Types considered scalar here are `int`, `bool`, `float`, `string` and `null`. ### `VarExporter::TRAILING_COMMA_IN_ARRAY` -Add trailing comma for last item of an array: +Adds a trailing comma after the last item of *non-inline* arrays: ```php -VarExporter::export([ - 'one' => ['hello', 'world', 123, true, false, null, 7.5], - 'two' => ['hello', 'world', ['one', 'two', 'three']] -], VarExporter::TRAILING_COMMA_IN_ARRAY | VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); +VarExporter::export( + ['hello', 'world', ['one', 'two', 'three']], + VarExporter::TRAILING_COMMA_IN_ARRAY | VarExporter::INLINE_NUMERIC_SCALAR_ARRAY +); ``` ```php [ - 'one' => ['hello', 'world', 123, true, false, null, 7.5], - 'two' => [ - 'hello', - 'world', - ['one', 'two', 'three'], - ], + 'hello', + 'world', + ['one', 'two', 'three'], ] ``` -Trailing comma won't be added to inlined arrays if this flag is used along with -`VarExporter::INLINE_NUMERIC_SCALAR_ARRAY`. - ### `VarExporter::CLOSURE_SNAPSHOT_USES` Export the current value of each `use()` variable as expression inside the exported closure. diff --git a/src/VarExporter.php b/src/VarExporter.php index 0185554..8dbcab3 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -59,8 +59,7 @@ final class VarExporter public const CLOSURE_SNAPSHOT_USES = 1 << 8; /** - * Add trailing comma for last item of an array. - * Trailing comma won't be added to inlined arrays even if this flag is used. + * Add a trailing comma after the last item of non-inline arrays. */ public const TRAILING_COMMA_IN_ARRAY = 1 << 9; From ae90b0ac429832d4dfed7a5bd3e01feb0d7de3f8 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 7 Feb 2021 23:50:44 +0100 Subject: [PATCH 046/129] Clean up phpdoc for non-existing parameter --- src/Internal/ObjectExporter/ClosureExporter.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index f2eb35c..7ffaf42 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -190,7 +190,6 @@ private function getClosure( * * @param ReflectionFunction $reflectionFunction Reflection of the closure. * @param Node\Expr\ArrowFunction $arrowFunction Parsed arrow function. - * @param string[] $path The path to the closure in the array/object graph. * * @return Node\Expr\Closure */ From 6dbe63af5126c96de48382fab784011863db9c32 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 7 Feb 2021 23:52:42 +0100 Subject: [PATCH 047/129] Update Psalm --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 58e1988..f627551 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "4.3.2" + "vimeo/psalm": "4.4.1" }, "autoload": { "psr-4": { From 9f3251599577e48bc1164b320b0140fcd3794d8f Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 9 Feb 2021 02:40:46 +0530 Subject: [PATCH 048/129] Metadata files (#18) --- .editorconfig | 18 ++++++++++++++++++ .gitattributes | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e8ed8f3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes index ca0e475..7b5e0e7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,8 @@ +.editorconfig export-ignore .gitattributes export-ignore .gitignore export-ignore -.travis.yml export-ignore phpunit.xml export-ignore +psalm.xml export-ignore README.md export-ignore +.github export-ignore tests/ export-ignore From 6635221249333339894ce3bf4c052b15890626c1 Mon Sep 17 00:00:00 2001 From: ADmad Date: Tue, 9 Feb 2021 14:24:00 +0530 Subject: [PATCH 049/129] Fix CI workflow file. Use pcov instead of xdebug for coverage reporting. It's much faster. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 217f771..b4dae91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} - coverage: xdebug + coverage: pcov - name: Install composer dependencies uses: "ramsey/composer-install@v1" @@ -64,7 +64,7 @@ jobs: - name: Run PHPUnit with coverage run: | - mkdir -p mkdir -p build/logs + mkdir -p build/logs vendor/bin/phpunit --coverage-clover build/logs/clover.xml if: ${{ matrix.php-version == '8.0' }} From c9f9854b902f5e2ad17d3f7cfd308a1c768cc381 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 10 Feb 2021 12:37:27 +0100 Subject: [PATCH 050/129] Mark public properties as readonly --- src/Internal/GenericExporter.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 23629ad..02e7852 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -33,26 +33,36 @@ final class GenericExporter private $visitedObjects = []; /** + * @psalm-readonly + * * @var bool */ public $addTypeHints; /** + * @psalm-readonly + * * @var bool */ public $skipDynamicProperties; /** + * @psalm-readonly + * * @var bool */ public $inlineNumericScalarArray; /** + * @psalm-readonly + * * @var bool */ public $closureSnapshotUses; /** + * @psalm-readonly + * * @var bool */ public $trailingCommaInArray; From 5e9e8caad4fe469c845444f622c8283f2a8300ce Mon Sep 17 00:00:00 2001 From: ADmad Date: Wed, 10 Feb 2021 19:06:22 +0530 Subject: [PATCH 051/129] Add argument to control the indentation level. (#17) --- README.md | 25 ++++ src/Internal/GenericExporter.php | 12 +- .../ObjectExporter/ClosureExporter.php | 2 +- src/VarExporter.php | 26 ++-- tests/VarExporterTest.php | 121 ++++++++++++++++++ 5 files changed, 176 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d67e0b1..eaa5213 100644 --- a/README.md +++ b/README.md @@ -497,6 +497,31 @@ VarExporter::export( Export the current value of each `use()` variable as expression inside the exported closure. +## Indentation + +You can use the 3rd argument of `VarExporter::export()` to control the indentation level. +This is useful when you want to use the generated code string to replace a placeholder in a template used to generate code files. + +So using output of `VarExporter::export(['foo' => 'bar'], 0, 1)` in the template below to replace `{{exported}}` + +``` +public foo() +{ + $data = {{exported}}; +} +``` + +would result in + +```php +public foo() +{ + $data = [ + 'foo' => 'bar' + ]; +} +``` + ## Error handling Any error occurring on `export()` will throw an `ExportException`: diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 02e7852..e665fd2 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -67,10 +67,18 @@ final class GenericExporter */ public $trailingCommaInArray; + /** + * @psalm-readonly + * + * @var int + */ + public $indentLevel; + /** * @param int $options + * @param int Indentation level */ - public function __construct(int $options) + public function __construct(int $options, int $indentLevel = 0) { $this->objectExporters[] = new ObjectExporter\StdClassExporter($this); @@ -97,6 +105,8 @@ public function __construct(int $options) $this->inlineNumericScalarArray = (bool) ($options & VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); $this->trailingCommaInArray = (bool) ($options & VarExporter::TRAILING_COMMA_IN_ARRAY); + + $this->indentLevel = $indentLevel; } /** diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 7ffaf42..6cfb9f5 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -54,7 +54,7 @@ public function export($object, \ReflectionObject $reflectionObject, array $path $closure = $this->getClosure($reflectionFunction, $ast, $file, $line, $path); $prettyPrinter = new ClosureExporter\PrettyPrinter(); - $prettyPrinter->setVarExporterNestingLevel(count($path)); + $prettyPrinter->setVarExporterNestingLevel(count($path) + $this->exporter->indentLevel); $code = $prettyPrinter->prettyPrintExpr($closure); diff --git a/src/VarExporter.php b/src/VarExporter.php index 8dbcab3..2a2bd4f 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -64,23 +64,33 @@ final class VarExporter public const TRAILING_COMMA_IN_ARRAY = 1 << 9; /** - * @param mixed $var The variable to export. - * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. - * Combine multiple options with a bitwise OR `|` operator. + * @param mixed $var The variable to export. + * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. + * Combine multiple options with a bitwise OR `|` operator. + * @param int $indentLevel Indentation level. * * @return string * * @throws ExportException */ - public static function export($var, int $options = 0) : string + public static function export($var, int $options = 0, int $indentLevel = 0) : string { - $exporter = new GenericExporter($options); - + $exporter = new GenericExporter($options, $indentLevel); $lines = $exporter->export($var, [], []); - $export = implode(PHP_EOL, $lines); + + if ($indentLevel < 1 || count($lines) < 2) { + $export = implode(PHP_EOL, $lines); + } else { + $firstLine = array_shift($lines); + $lines = array_map(function ($line) use ($indentLevel) { + return str_repeat(' ', $indentLevel) . $line; + }, $lines); + + $export = $firstLine . PHP_EOL . implode(PHP_EOL, $lines); + } if ($options & self::ADD_RETURN) { - return 'return ' . $export . ';' . PHP_EOL; + return 'return ' . $export . ';' . PHP_EOL; } return $export; diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 7462d23..4940f0f 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -287,4 +287,125 @@ public function testExportObjectWithCircularReference() ] ]); } + + /** + * @dataProvider providerExportIndented + */ + public function testExportIndented($var, $expected, $options) + { + $template = <<<'TPL' +public foo () +{ + $data = {{exported}}; +} +TPL; + + $exported = VarExporter::export($var, $options, 1); + $result = str_replace('{{exported}}', $exported, $template); + + $this->assertEquals($expected, $result); + } + + public function providerExportIndented() + { + // Array + $var = ['one' => ['hello', true], 'two' => 2]; + $expected = <<<'PHP' +public foo () +{ + $data = [ + 'one' => [ + 'hello', + true + ], + 'two' => 2 + ]; +} +PHP; + yield [$var, $expected, 0]; + + // Null + $var = null; + $expected = <<<'PHP' +public foo () +{ + $data = null; +} +PHP; + yield [$var, $expected, 0]; + + // Closure + $var = function () { + return 'Hello, world!'; + }; + $expected = <<<'PHP' +public foo () +{ + $data = function () { + return 'Hello, world!'; + }; +} +PHP; + yield [$var, $expected, 0]; + + $foo = 'bar'; + $sub = function () use ($foo) { + return $foo; + }; + $var = function () use ($sub) { + return $sub(); + }; + + $expected = <<<'PHP' +public foo () +{ + $data = function () { + $sub = function () { + $foo = 'bar'; + return $foo; + }; + return $sub(); + }; +} +PHP; + yield [$var, $expected, VarExporter::CLOSURE_SNAPSHOT_USES]; + + $var = function () { + $a = 'Hello, +World!'; + + $b = << Date: Wed, 10 Feb 2021 14:43:41 +0100 Subject: [PATCH 052/129] Minor tweaks --- README.md | 2 +- src/VarExporter.php | 2 +- tests/VarExporterTest.php | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index eaa5213..0689cb1 100644 --- a/README.md +++ b/README.md @@ -502,7 +502,7 @@ Export the current value of each `use()` variable as expression inside the expor You can use the 3rd argument of `VarExporter::export()` to control the indentation level. This is useful when you want to use the generated code string to replace a placeholder in a template used to generate code files. -So using output of `VarExporter::export(['foo' => 'bar'], 0, 1)` in the template below to replace `{{exported}}` +So using output of `VarExporter::export(['foo' => 'bar'], indentLevel: 1)` in the template below to replace `{{exported}}` ``` public foo() diff --git a/src/VarExporter.php b/src/VarExporter.php index 2a2bd4f..c271768 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -67,7 +67,7 @@ final class VarExporter * @param mixed $var The variable to export. * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. * Combine multiple options with a bitwise OR `|` operator. - * @param int $indentLevel Indentation level. + * @param int $indentLevel The base output indentation level. * * @return string * diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 4940f0f..7b5846a 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -294,7 +294,7 @@ public function testExportObjectWithCircularReference() public function testExportIndented($var, $expected, $options) { $template = <<<'TPL' -public foo () +public function foo() { $data = {{exported}}; } @@ -311,7 +311,7 @@ public function providerExportIndented() // Array $var = ['one' => ['hello', true], 'two' => 2]; $expected = <<<'PHP' -public foo () +public function foo() { $data = [ 'one' => [ @@ -327,7 +327,7 @@ public function providerExportIndented() // Null $var = null; $expected = <<<'PHP' -public foo () +public function foo() { $data = null; } @@ -339,7 +339,7 @@ public function providerExportIndented() return 'Hello, world!'; }; $expected = <<<'PHP' -public foo () +public function foo() { $data = function () { return 'Hello, world!'; @@ -357,7 +357,7 @@ public function providerExportIndented() }; $expected = <<<'PHP' -public foo () +public function foo() { $data = function () { $sub = function () { @@ -388,7 +388,7 @@ public function providerExportIndented() }; $expected = <<<'PHP' -public foo () +public function foo() { $data = function () { $a = 'Hello, From a35c0a0906aacd3da39da53cbfc8e455bbb74df0 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 10 Feb 2021 14:48:53 +0100 Subject: [PATCH 053/129] Add types to tests --- tests/ExportClosureTest.php | 26 +++++++++++----------- tests/ExportObjectTest.php | 44 ++++++++++++++++++------------------- tests/VarExporterTest.php | 28 ++++++++++++----------- 3 files changed, 50 insertions(+), 48 deletions(-) diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index becc5b3..c00ddb7 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -20,7 +20,7 @@ */ class ExportClosureTest extends AbstractTestCase { - public function testExportSimpleClosure() + public function testExportSimpleClosure(): void { $var = function() { return 'Hello, world!'; @@ -35,7 +35,7 @@ function () { $this->assertExportEquals($expected, $var); } - public function testExportNestedComplexClosure() + public function testExportNestedComplexClosure(): void { $var = [ (object) [ @@ -76,7 +76,7 @@ public function testExportNestedComplexClosure() $this->assertExportEquals($expected, $var); } - public function testExportNamespacedCode() + public function testExportNamespacedCode(): void { $var = function(SetState $a) : array { return [ @@ -104,7 +104,7 @@ function (\Brick\VarExporter\Tests\Classes\SetState $a) : array { $this->assertExportEquals($expected, $var); } - public function testExportClosureWithStringsContainingLikeBreaks() + public function testExportClosureWithStringsContainingLikeBreaks(): void { $var = function() { $a = 'Hello, @@ -143,7 +143,7 @@ public function testExportClosureWithStringsContainingLikeBreaks() $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN); } - public function testExportClosureWithUse() + public function testExportClosureWithUse(): void { $foo = 'bar'; @@ -158,7 +158,7 @@ public function testExportClosureWithUse() ); } - public function testExportClosureWithUseAsVars() + public function testExportClosureWithUseAsVars(): void { $foo = 'b' . 'a' . 'r'; @@ -177,7 +177,7 @@ public function testExportClosureWithUseAsVars() $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); } - public function testExportClosureWithUseClosure() + public function testExportClosureWithUseClosure(): void { $foo = 'b' . 'a' . 'r'; @@ -204,7 +204,7 @@ public function testExportClosureWithUseClosure() } - public function testExportArrowFunction() + public function testExportArrowFunction(): void { if (version_compare(PHP_VERSION, '7.4.0') < 0) { $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); @@ -224,7 +224,7 @@ function ($planet) { $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN); } - public function testExportArrowFunctionWithContext() + public function testExportArrowFunctionWithContext(): void { if (version_compare(PHP_VERSION, '7.4.0') < 0) { $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); @@ -241,7 +241,7 @@ public function testExportArrowFunctionWithContext() ); } - public function testExportArrowFunctionWithContextVarAsVar() + public function testExportArrowFunctionWithContextVarAsVar(): void { if (version_compare(PHP_VERSION, '7.4.0') < 0) { $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); @@ -264,7 +264,7 @@ function ($planet) { $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); } - public function testExportClosureDefinedInEval() + public function testExportClosureDefinedInEval(): void { $var = eval(<<assertExportThrows("Closure defined in eval()'d code cannot be exported.", $var); } - public function testExportTwoClosuresOnSameLine() + public function testExportTwoClosuresOnSameLine(): void { $var = function() { return function() {}; }; $this->assertExportThrows("Expected exactly 1 closure in */tests/ExportClosureTest.php on line *, found 2.", $var); } - public function testExportClosureDisabled() + public function testExportClosureDisabled(): void { $var = function() { return 'Hello, world!'; diff --git a/tests/ExportObjectTest.php b/tests/ExportObjectTest.php index f487fb5..9c79f6f 100644 --- a/tests/ExportObjectTest.php +++ b/tests/ExportObjectTest.php @@ -21,7 +21,7 @@ */ class ExportObjectTest extends AbstractTestCase { - public function testExportStdClass() + public function testExportStdClass(): void { $object = new \stdClass; $object->foo = 'Hello'; @@ -42,7 +42,7 @@ public function testExportStdClass() $this->assertExportEquals($expected, $object); } - public function testExportClassWithNoProperties() + public function testExportClassWithNoProperties(): void { $object = new NoProperties; @@ -51,7 +51,7 @@ public function testExportClassWithNoProperties() $this->assertExportEquals($expected, $object); } - public function testExportClassWithDynamicPropertiesOnly() + public function testExportClassWithDynamicPropertiesOnly(): void { $object = new NoProperties; $object->x = 1.0; @@ -75,7 +75,7 @@ public function testExportClassWithDynamicPropertiesOnly() $this->assertExportEquals($expected, $object); } - public function testExportClassWithDynamicPropertiesOnly_SkipDynamicProperties() + public function testExportClassWithDynamicPropertiesOnly_SkipDynamicProperties(): void { $object = new NoProperties; $object->dynamicProp = 'Hello'; @@ -86,7 +86,7 @@ public function testExportClassWithDynamicPropertiesOnly_SkipDynamicProperties() $this->assertExportEquals($expected, $object, VarExporter::SKIP_DYNAMIC_PROPERTIES); } - public function testExportClassWithConstructorAndNoProperties() + public function testExportClassWithConstructorAndNoProperties(): void { $object = new ConstructorAndNoProperties(); @@ -102,7 +102,7 @@ public function testExportClassWithConstructorAndNoProperties() $this->assertExportEquals($expected, $object, VarExporter::SKIP_DYNAMIC_PROPERTIES); } - public function testExportClassWithPublicPropertiesOnly() + public function testExportClassWithPublicPropertiesOnly(): void { $object = new PublicPropertiesOnly; $object->foo = 'Hello'; @@ -122,7 +122,7 @@ public function testExportClassWithPublicPropertiesOnly() $this->assertExportEquals($expected, $object); } - public function testExportObjectWithPublicAndDynamicProperties() + public function testExportObjectWithPublicAndDynamicProperties(): void { $object = new PublicPropertiesOnly; $object->foo = 'Hello'; @@ -144,7 +144,7 @@ public function testExportObjectWithPublicAndDynamicProperties() $this->assertExportEquals($expected, $object); } - public function testExportObjectWithPublicAndDynamicProperties_SkipDynamicProperties() + public function testExportObjectWithPublicAndDynamicProperties_SkipDynamicProperties(): void { $object = new PublicPropertiesOnly; $object->foo = 'Hello'; @@ -165,7 +165,7 @@ public function testExportObjectWithPublicAndDynamicProperties_SkipDynamicProper $this->assertExportEquals($expected, $object, VarExporter::SKIP_DYNAMIC_PROPERTIES); } - public function testExportClassWithSetState() + public function testExportClassWithSetState(): void { $object = new SetState; $object->foo = 'Hello'; @@ -182,7 +182,7 @@ public function testExportClassWithSetState() $this->assertExportEquals($expected, $object); } - public function testExportClassWithSetStateAndUnsetProperties() + public function testExportClassWithSetStateAndUnsetProperties(): void { $object = new SetState; $object->foo = null; @@ -199,7 +199,7 @@ public function testExportClassWithSetStateAndUnsetProperties() $this->assertExportEquals($expected, $object); } - public function testExportClassWithSetStateAndDynamicProperties() + public function testExportClassWithSetStateAndDynamicProperties(): void { $object = new SetState; $object->foo = 'Hello'; @@ -220,7 +220,7 @@ public function testExportClassWithSetStateAndDynamicProperties() $this->assertExportEquals($expected, $object); } - public function testExportClassWithSetStateAndDynamicProperties_SkipDynamicProperties() + public function testExportClassWithSetStateAndDynamicProperties_SkipDynamicProperties(): void { $object = new SetState; $object->foo = 'Hello'; @@ -238,7 +238,7 @@ public function testExportClassWithSetStateAndDynamicProperties_SkipDynamicPrope $this->assertExportEquals($expected, $object, VarExporter::SKIP_DYNAMIC_PROPERTIES); } - public function testExportClassWithSetStateAndOverriddenPrivateProperties() + public function testExportClassWithSetStateAndOverriddenPrivateProperties(): void { $object = new SetStateWithOverriddenPrivateProperties; @@ -247,7 +247,7 @@ public function testExportClassWithSetStateAndOverriddenPrivateProperties() $this->assertExportThrows($expectedMessage, $object); } - public function testExportClassWithPrivateConstructor() + public function testExportClassWithPrivateConstructor(): void { $object = PrivateConstructor::create(); $object->foo = 'Foo'; @@ -268,7 +268,7 @@ public function testExportClassWithPrivateConstructor() $this->assertExportEquals($expected, $object); } - public function testExportClassWithPublicPropertiesAndConstructor() + public function testExportClassWithPublicPropertiesAndConstructor(): void { $object = new PublicPropertiesWithConstructor(); @@ -287,7 +287,7 @@ public function testExportClassWithPublicPropertiesAndConstructor() $this->assertExportEquals($expected, $object); } - public function testExportClassWithSerializeMagicMethods() + public function testExportClassWithSerializeMagicMethods(): void { $object = new SerializeMagicMethods; @@ -313,7 +313,7 @@ public function testExportClassWithSerializeMagicMethods() $this->assertExportEquals($expected, $object); } - public function testExportClassWithSerializeMagicMethodsAndConstructor() + public function testExportClassWithSerializeMagicMethodsAndConstructor(): void { $object = new SerializeMagicMethodsWithConstructor('Test', 1234); @@ -334,7 +334,7 @@ public function testExportClassWithSerializeMagicMethodsAndConstructor() $this->assertExportEquals($expected, $object); } - public function testExportClassWithSerializeMagicMethodsAndConstructor_AddTypeHints() + public function testExportClassWithSerializeMagicMethodsAndConstructor_AddTypeHints(): void { $object = new SerializeMagicMethodsWithConstructor('Test', 1234); @@ -357,7 +357,7 @@ public function testExportClassWithSerializeMagicMethodsAndConstructor_AddTypeHi $this->assertExportEquals($expected, $object, VarExporter::ADD_TYPE_HINTS); } - public function testExportClassHierarchy() + public function testExportClassHierarchy(): void { $object = Hierarchy\C::create(); $object->dynamicProperty = 'A property declared dynamically'; @@ -397,7 +397,7 @@ public function testExportClassHierarchy() $this->assertExportEquals($expected, $object); } - public function testExportClassHierarchy_AddTypeHints_SkipDynamicProperties() + public function testExportClassHierarchy_AddTypeHints_SkipDynamicProperties(): void { $object = Hierarchy\C::create(); $object->dynamicProperty = 'A property declared dynamically'; @@ -441,7 +441,7 @@ public function testExportClassHierarchy_AddTypeHints_SkipDynamicProperties() $this->assertExportEquals($expected, $object, VarExporter::ADD_TYPE_HINTS | VarExporter::SKIP_DYNAMIC_PROPERTIES); } - public function testExportClassHierarchyWithUnsetProperties() + public function testExportClassHierarchyWithUnsetProperties(): void { $object = Hierarchy\C::create(); @@ -493,7 +493,7 @@ public function testExportClassHierarchyWithUnsetProperties() $this->assertExportEquals($expected, $object); } - public function testExportObjectWithRestrictiveOptions() + public function testExportObjectWithRestrictiveOptions(): void { $object = new PublicPropertiesOnly(); diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 7b5846a..fb55120 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -11,7 +11,7 @@ class VarExporterTest extends AbstractTestCase { - public function testMixedVar() + public function testMixedVar(): void { $myObject = new PublicPropertiesOnly(); $myObject->foo = 'hello'; @@ -102,7 +102,7 @@ public function testMixedVar() $this->assertExportEquals($expected, $var); } - public function testExportObjectPropWithSpecialChars() + public function testExportObjectPropWithSpecialChars(): void { $object = new PublicPropertiesOnly; $object->{'$ref'} = '#/components/schemas/User'; @@ -122,7 +122,7 @@ public function testExportObjectPropWithSpecialChars() $this->assertExportEquals($expected, $object); } - public function testAddReturn() + public function testAddReturn(): void { $var = []; $expected = 'return [];' . PHP_EOL; @@ -130,7 +130,7 @@ public function testAddReturn() $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN); } - public function testInlineNumericScalarArray() + public function testInlineNumericScalarArray(): void { $var = [ 'one' => ['hello', 'world', 123, true, false, null, 7.5], @@ -151,7 +151,7 @@ public function testInlineNumericScalarArray() $this->assertExportEquals($expected, $var, VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); } - public function testTrailingCommaInArray() + public function testTrailingCommaInArray(): void { $var = [ 'one' => ['hello', 'world', 123, true, false, null, 7.5], @@ -172,7 +172,7 @@ public function testTrailingCommaInArray() $this->assertExportEquals($expected, $var, VarExporter::INLINE_NUMERIC_SCALAR_ARRAY | VarExporter::TRAILING_COMMA_IN_ARRAY); } - public function testExportDateTime() + public function testExportDateTime(): void { $timezone = new \DateTimeZone('Europe/Berlin'); $format = 'Y-m-d H:i:s.u'; @@ -190,7 +190,7 @@ public function testExportDateTime() $this->assertExportEquals($expected, $var); } - public function testExportDateTimeImmutable() + public function testExportDateTimeImmutable(): void { $timezone = new \DateTimeZone('Europe/Berlin'); $format = 'Y-m-d H:i:s.u'; @@ -208,7 +208,7 @@ public function testExportDateTimeImmutable() $this->assertExportEquals($expected, $var); } - public function testExportInternalClass() + public function testExportInternalClass(): void { $object = new \stdClass; $object->iterator = new \ArrayIterator(); @@ -218,7 +218,7 @@ public function testExportInternalClass() $this->assertExportThrows($expectedMessage, $object); } - public function testExportResource() + public function testExportResource(): void { $handle = fopen('php://memory', 'rb+'); @@ -235,7 +235,7 @@ public function testExportResource() VarExporter::export($object); } - public function testExportObjectTwiceWithoutCircularReference() + public function testExportObjectTwiceWithoutCircularReference(): void { $a = new PublicPropertiesOnly; $a->foo = 'Foo'; @@ -270,7 +270,7 @@ public function testExportObjectTwiceWithoutCircularReference() $this->assertExportEquals($expected, $var); } - public function testExportObjectWithCircularReference() + public function testExportObjectWithCircularReference(): void { $a = new PublicPropertiesOnly; $b = new PublicPropertiesOnly; @@ -290,8 +290,10 @@ public function testExportObjectWithCircularReference() /** * @dataProvider providerExportIndented + * + * @param mixed $var */ - public function testExportIndented($var, $expected, $options) + public function testExportIndented($var, string $expected, int $options): void { $template = <<<'TPL' public function foo() @@ -306,7 +308,7 @@ public function foo() $this->assertEquals($expected, $result); } - public function providerExportIndented() + public function providerExportIndented(): iterable { // Array $var = ['one' => ['hello', true], 'two' => 2]; From bc09fd6bab4d476142fba521018352ba3079d3bb Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 10 Feb 2021 14:53:07 +0100 Subject: [PATCH 054/129] Add note about indentation of first line --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0689cb1..84d52fe 100644 --- a/README.md +++ b/README.md @@ -502,7 +502,7 @@ Export the current value of each `use()` variable as expression inside the expor You can use the 3rd argument of `VarExporter::export()` to control the indentation level. This is useful when you want to use the generated code string to replace a placeholder in a template used to generate code files. -So using output of `VarExporter::export(['foo' => 'bar'], indentLevel: 1)` in the template below to replace `{{exported}}` +So using output of `VarExporter::export(['foo' => 'bar'], indentLevel: 1)` in the template below to replace `{{exported}}`: ``` public foo() @@ -511,7 +511,7 @@ public foo() } ``` -would result in +would result in: ```php public foo() @@ -522,6 +522,8 @@ public foo() } ``` +Note that the first line will never be indented, as we can see in the example above. + ## Error handling Any error occurring on `export()` will throw an `ExportException`: From 1ab6a3fdd7072e363f5fb5ff9a9d665e913b10fe Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 10 Feb 2021 14:57:54 +0100 Subject: [PATCH 055/129] Add downloads badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 84d52fe..08538ea 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A powerful and pretty replacement for PHP's `var_export()`. [![Build Status](https://github.com/brick/varexporter/workflows/CI/badge.svg)](https://github.com/brick/varexporter/actions) [![Coverage Status](https://coveralls.io/repos/github/brick/varexporter/badge.svg?branch=master)](https://coveralls.io/github/brick/varexporter?branch=master) [![Latest Stable Version](https://poser.pugx.org/brick/varexporter/v/stable)](https://packagist.org/packages/brick/varexporter) +[![Total Downloads](https://poser.pugx.org/brick/varexporter/downloads)](https://packagist.org/packages/brick/varexporter) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) ## Introduction From 0611a6c300d911e0429cd0ea7eb9c6cbfdaaf025 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sat, 20 Mar 2021 18:35:40 +0100 Subject: [PATCH 056/129] Add GitHub Sponsors --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..689b999 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: BenMorel From f32d734b09f61f8d276d6cb4128574c8aa2c3c43 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Mon, 21 Jun 2021 01:18:12 +0200 Subject: [PATCH 057/129] Update Psalm --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f627551..fa3b3e0 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "4.4.1" + "vimeo/psalm": "4.8.1" }, "autoload": { "psr-4": { From 718fdc87fb644b410dfbf5ffec90c5ffa2eefedd Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 24 Jun 2021 00:21:48 +0200 Subject: [PATCH 058/129] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 08538ea..3d867e7 100644 --- a/README.md +++ b/README.md @@ -336,7 +336,7 @@ function (\My\App\Service $service) : \My\App\Model\Entity { } ``` -Note how all namespaced classes, and explicitly namespaced functions and constants, have been rewritten, while the non-namespaced function `strlen()` and the non-namespaced constant have been left as is. This brings us to the first caveat: +Note how all namespaced classes, and explicitly namespaced functions and constants, have been rewritten, while the non-namespaced function `strlen()` and the non-namespaced constant have been left as is. Please see the first [caveat](#caveats). ### Use statements @@ -374,7 +374,7 @@ $planet = 'world'; echo VarExporter::export([ 'callback' => fn(string $greeting) => $greeting . ', ' . $planet . '!'; -], VarExported::CLOSURE_SNAPSHOT_USES); +], VarExporter::CLOSURE_SNAPSHOT_USES); ``` ```php @@ -388,7 +388,7 @@ echo VarExporter::export([ ### Caveats -- **Functions and constants that are not not explicitly namespaced**, either directly or through a `use function` or `use const` statement, **are always exported as is**. This is because the parser does not have the runtime context to check if a definition for this function or constant exists in the current namespace, and as such cannot reliably predict the behaviour of PHP's [fallback to global function/constant](https://www.php.net/manual/en/language.namespaces.fallback.php). Be really careful here if you're using namespaced functions or constants: **always explicitly import your namespaced functions and constants**, if any. +- **Functions and constants that are not explicitly namespaced**, either directly or through a `use function` or `use const` statement, **are always exported as is**. This is because the parser does not have the runtime context to check if a definition for this function or constant exists in the current namespace, and as such cannot reliably predict the behaviour of PHP's [fallback to global function/constant](https://www.php.net/manual/en/language.namespaces.fallback.php). Be really careful here if you're using namespaced functions or constants: **always explicitly import your namespaced functions and constants**, if any. - Closures can use `$this`, but **will not be bound to an object once exported**. You must explicitly bind them through [`bindTo()`](https://www.php.net/manual/en/closure.bindto.php) if required, after running the exported code. - **You cannot have 2 closures on the same line in your source file**, or an `ExportException` will be thrown. This is because `VarExporter` cannot know which one holds the definition for the `\Closure` object it encountered. - **Closures defined in eval()'d code cannot be exported** and throw an `ExportException`, because there is no source file to parse. From 42831ae042b551ef8387d4c0721c00962df0c110 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 27 Jul 2021 12:23:20 +0200 Subject: [PATCH 059/129] Run CI on all branches --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4dae91..c780b5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,7 @@ name: CI on: push: - branches: [ master ] pull_request: - branches: [ master ] jobs: psalm: From 48e2c40c45ee252f400538183652b70ee0190b61 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 13 Oct 2021 23:11:09 +0200 Subject: [PATCH 060/129] Test on PHP 8.1 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c780b5f..de3e318 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ jobs: - "7.3" - "7.4" - "8.0" + - "8.1" deps: - "highest" include: From bb624f1d2d362b5addde26fb3a6d0fd0038b6961 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 17 Oct 2021 11:25:18 +0200 Subject: [PATCH 061/129] Fix tests on PHP 8.1 --- tests/ExportObjectTest.php | 49 +++++++++++++++++++++++++++++++--- tests/VarExporterTest.php | 54 +++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/tests/ExportObjectTest.php b/tests/ExportObjectTest.php index 9c79f6f..66bc6d0 100644 --- a/tests/ExportObjectTest.php +++ b/tests/ExportObjectTest.php @@ -171,13 +171,23 @@ public function testExportClassWithSetState(): void $object->foo = 'Hello'; $object->bar = 'World'; - $expected = <<<'PHP' + if (PHP_VERSION_ID >=80100) { + $expected = <<<'PHP' +\Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => 'Hello', + 'bar' => 'World', + 'baz' => 'defaultValue' +]) +PHP; + } else { + $expected = <<<'PHP' \Brick\VarExporter\Tests\Classes\SetState::__set_state([ 'baz' => 'defaultValue', 'foo' => 'Hello', 'bar' => 'World' ]) PHP; + } $this->assertExportEquals($expected, $object); } @@ -189,12 +199,21 @@ public function testExportClassWithSetStateAndUnsetProperties(): void unset($object->bar); - $expected = <<<'PHP' + if (PHP_VERSION_ID >= 80100) { + $expected = <<<'PHP' +\Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => null, + 'baz' => 'defaultValue' +]) +PHP; + } else { + $expected = <<<'PHP' \Brick\VarExporter\Tests\Classes\SetState::__set_state([ 'baz' => 'defaultValue', 'foo' => null ]) PHP; + } $this->assertExportEquals($expected, $object); } @@ -207,7 +226,18 @@ public function testExportClassWithSetStateAndDynamicProperties(): void $object->dynamic = 'Dynamic property'; $object->{'123'} = 'Numeric dynamic property'; - $expected = <<<'PHP' + if (PHP_VERSION_ID >= 80100) { + $expected = <<<'PHP' +\Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => 'Hello', + 'bar' => 'World', + 'baz' => 'defaultValue', + 'dynamic' => 'Dynamic property', + 123 => 'Numeric dynamic property' +]) +PHP; + } else { + $expected = <<<'PHP' \Brick\VarExporter\Tests\Classes\SetState::__set_state([ 'baz' => 'defaultValue', 'foo' => 'Hello', @@ -216,6 +246,7 @@ public function testExportClassWithSetStateAndDynamicProperties(): void 123 => 'Numeric dynamic property' ]) PHP; + } $this->assertExportEquals($expected, $object); } @@ -227,13 +258,23 @@ public function testExportClassWithSetStateAndDynamicProperties_SkipDynamicPrope $object->bar = 'World'; $object->dynamic = 'Dynamic property'; - $expected = <<<'PHP' + if (PHP_VERSION_ID >= 80100) { + $expected = <<<'PHP' +\Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => 'Hello', + 'bar' => 'World', + 'baz' => 'defaultValue' +]) +PHP; + } else { + $expected = <<<'PHP' \Brick\VarExporter\Tests\Classes\SetState::__set_state([ 'baz' => 'defaultValue', 'foo' => 'Hello', 'bar' => 'World' ]) PHP; + } $this->assertExportEquals($expected, $object, VarExporter::SKIP_DYNAMIC_PROPERTIES); } diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index fb55120..99792b6 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -49,7 +49,58 @@ public function testMixedVar(): void 'aCustomObject' => $myObject ]; - $expected = <<<'PHP' + if (PHP_VERSION_ID >= 80100) { + $expected = <<<'PHP' +[ + 'aString' => 'Hello', + 'aTrue' => true, + 'aFalse' => false, + 'aNull' => null, + 'aFloat' => 0.75, + 'anInt' => 123, + 'aNumericArray' => [ + 'a', + 'b', + null, + [ + 'c' => 'd' + ] + ], + 'anAssociativeArray' => [ + 'a' => 'b', + 'c' => [ + 'd' => 'e', + 'f' => [ + 'g' => [ + [] + ] + ] + ] + ], + 'anObject' => (object) [ + 'type' => 'string', + '$ref' => '#/components/schema/User', + 'items' => (object) [ + 'foo' => 'bar', + 'empty' => (object) [] + ] + ], + 'aCustomObject' => (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; + + $object->foo = 'hello'; + $object->bar = \Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => 'SetState.foo', + 'bar' => 'SetState.bar', + 'baz' => 'defaultValue' + ]); + + return $object; + })() +] +PHP; + } else { + $expected = <<<'PHP' [ 'aString' => 'Hello', 'aTrue' => true, @@ -98,6 +149,7 @@ public function testMixedVar(): void })() ] PHP; + } $this->assertExportEquals($expected, $var); } From ecdc6466c5b22026b22a13527931036c03919806 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Wed, 15 Jun 2022 21:02:46 +0200 Subject: [PATCH 062/129] Add missing object type-hints --- src/Internal/ObjectExporter/AnyObjectExporter.php | 2 +- src/Internal/ObjectExporter/ClosureExporter.php | 2 +- src/Internal/ObjectExporter/InternalClassExporter.php | 2 +- src/Internal/ObjectExporter/SerializeExporter.php | 2 +- src/Internal/ObjectExporter/SetStateExporter.php | 2 +- src/Internal/ObjectExporter/StdClassExporter.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index 5449e71..090393e 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -29,7 +29,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool * * @psalm-suppress MixedAssignment */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $lines = $this->getCreateObjectCode($reflectionObject); diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 6cfb9f5..ea98891 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -39,7 +39,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { assert($object instanceof Closure); diff --git a/src/Internal/ObjectExporter/InternalClassExporter.php b/src/Internal/ObjectExporter/InternalClassExporter.php index 1deb23e..e0adbd1 100644 --- a/src/Internal/ObjectExporter/InternalClassExporter.php +++ b/src/Internal/ObjectExporter/InternalClassExporter.php @@ -25,7 +25,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $className = $reflectionObject->getName(); diff --git a/src/Internal/ObjectExporter/SerializeExporter.php b/src/Internal/ObjectExporter/SerializeExporter.php index 7c826c3..604a104 100644 --- a/src/Internal/ObjectExporter/SerializeExporter.php +++ b/src/Internal/ObjectExporter/SerializeExporter.php @@ -25,7 +25,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $lines = $this->getCreateObjectCode($reflectionObject); diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index 4963dd2..a464b99 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -31,7 +31,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $className = $reflectionObject->getName(); diff --git a/src/Internal/ObjectExporter/StdClassExporter.php b/src/Internal/ObjectExporter/StdClassExporter.php index 52fc0a9..ac958f1 100644 --- a/src/Internal/ObjectExporter/StdClassExporter.php +++ b/src/Internal/ObjectExporter/StdClassExporter.php @@ -24,7 +24,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool /** * {@inheritDoc} */ - public function export($object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $exported = $this->exporter->exportArray((array) $object, $path, $parentIds); From a5a0f9e2a3e270c7bdfc04279266504f3b863327 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 16 Jun 2022 01:08:45 +0200 Subject: [PATCH 063/129] Update Psalm --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index fa3b3e0..916985d 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "4.8.1" + "vimeo/psalm": "4.23.0" }, "autoload": { "psr-4": { From 06f837f8faed35f5649a15029a4b5886508b80a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Potock=C3=BD?= Date: Wed, 15 Jun 2022 17:04:30 +0200 Subject: [PATCH 064/129] Add support for exporting Enums when using PHP8.1+ --- src/Internal/GenericExporter.php | 4 ++ src/Internal/ObjectExporter/EnumExporter.php | 44 ++++++++++++++++++++ src/VarExporter.php | 5 +++ tests/Classes/Enum.php | 10 +++++ tests/ExportObjectTest.php | 15 +++++++ 5 files changed, 78 insertions(+) create mode 100644 src/Internal/ObjectExporter/EnumExporter.php create mode 100644 tests/Classes/Enum.php diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index e665fd2..19e8cc8 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -96,6 +96,10 @@ public function __construct(int $options, int $indentLevel = 0) $this->objectExporters[] = new ObjectExporter\SerializeExporter($this); } + if (! ($options & VarExporter::NO_ENUMS)) { + $this->objectExporters[] = new ObjectExporter\EnumExporter($this); + } + if (! ($options & VarExporter::NOT_ANY_OBJECT)) { $this->objectExporters[] = new ObjectExporter\AnyObjectExporter($this); } diff --git a/src/Internal/ObjectExporter/EnumExporter.php b/src/Internal/ObjectExporter/EnumExporter.php new file mode 100644 index 0000000..6c47358 --- /dev/null +++ b/src/Internal/ObjectExporter/EnumExporter.php @@ -0,0 +1,44 @@ +isEnum(); + } + + /** + * {@inheritDoc} + */ + public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + { + assert($object instanceof UnitEnum); + + return [ + get_class($object) . '::' . $object->name + ]; + } +} diff --git a/src/VarExporter.php b/src/VarExporter.php index c271768..83b0892 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -63,6 +63,11 @@ final class VarExporter */ public const TRAILING_COMMA_IN_ARRAY = 1 << 9; + /** + * Disallows exporting enums. + */ + public const NO_ENUMS = 1 << 10; + /** * @param mixed $var The variable to export. * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. diff --git a/tests/Classes/Enum.php b/tests/Classes/Enum.php new file mode 100644 index 0000000..850403d --- /dev/null +++ b/tests/Classes/Enum.php @@ -0,0 +1,10 @@ +assertExportThrows($expectedMessage, $object, VarExporter::NOT_ANY_OBJECT); } + + /** + * @requires PHP 8.1 + */ + public function testExportEnum(): void + { + $object = Enum::TEST; + + $expected = <<<'PHP' +Brick\VarExporter\Tests\Classes\Enum::TEST +PHP; + + $this->assertExportEquals($expected, $object); + } } From af330e79e0d6ea578c77b5424bba64ece619cd7b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 16 Jun 2022 01:51:29 +0200 Subject: [PATCH 065/129] Run Psalm on PHP 8.1 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de3e318..841e64e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: "8.0" + php-version: "8.1" - name: Install composer dependencies uses: "ramsey/composer-install@v1" From 754d11c4da45000b1de8169487bf6342898ec4c9 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 26 Jun 2022 00:24:47 +0200 Subject: [PATCH 066/129] Run coverage on PHP 8.1 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 841e64e..7da574c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,16 +59,16 @@ jobs: - name: Run PHPUnit run: vendor/bin/phpunit - if: ${{ matrix.php-version != '8.0' }} + if: ${{ matrix.php-version != '8.1' }} - name: Run PHPUnit with coverage run: | mkdir -p build/logs vendor/bin/phpunit --coverage-clover build/logs/clover.xml - if: ${{ matrix.php-version == '8.0' }} + if: ${{ matrix.php-version == '8.1' }} - name: Upload coverage report to Coveralls run: vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: ${{ matrix.php-version == '8.0' }} + if: ${{ matrix.php-version == '8.1' }} From cd6402ecbb970cb2a93a35b34908dc08b223887a Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 30 Jun 2022 01:00:26 +0200 Subject: [PATCH 067/129] Add CHANGELOG --- CHANGELOG.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..90bd94b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,102 @@ +# Changelog + +## [0.3.6](https://github.com/brick/varexporter/releases/tag/0.3.6) - 2022-06-15 + +✨ **New feature** + +Support for PHP 8.1 enums (#23). + +Thanks @Jacobs63! + +## [0.3.5](https://github.com/brick/varexporter/releases/tag/0.3.5) - 2021-02-10 + +✨ **New feature** + +Support for controlling the base indentation level (#17). + +Thanks @ADmad! + +## [0.3.4](https://github.com/brick/varexporter/releases/tag/0.3.4) - 2021-02-07 + +✨ **New feature** + +Support for trailing comma in non-inline arrays, with the `TRAILING_COMMA_IN_ARRAY` flag (#16). + +Thanks @ADmad! + +## [0.3.3](https://github.com/brick/varexporter/releases/tag/0.3.3) - 2020-12-24 + +🐛 **Bug fix** + +- Exporting an object with numeric dynamic properties would lead to a `TypeError` + +## [0.3.2](https://github.com/brick/varexporter/releases/tag/0.3.2) - 2020-03-13 + +✨ **New feature** + +Support for exporting internal classes implementing `__set_state()`: + +- `DateTime` +- `DateTimeImmutable` +- `DateTimeZone` +- `DateInterval` +- `DatePeriod` + +Thanks @GameplayJDK! + +## [0.3.1](https://github.com/brick/varexporter/releases/tag/0.3.1) - 2020-01-23 + +✨ **New features** + +- Support for closures with `use()` using the `CLOSURE_SNAPSHOT_USE` option (#7) +- Support for arrow functions in PHP 7.4 (#8) + +Thanks to @jasny for his awesome work! + +## [0.3.0](https://github.com/brick/varexporter/releases/tag/0.3.0) - 2019-12-24 + +Minimum PHP version is now `7.2`. No breaking changes. + +## [0.2.1](https://github.com/brick/varexporter/releases/tag/0.2.1) - 2019-04-16 + +✨ **New option**: `VarExporter::INLINE_NUMERIC_SCALAR_ARRAY` (#3) + +Formats numeric arrays containing only scalar values on a single line. + +## [0.2.0](https://github.com/brick/varexporter/releases/tag/0.2.0) - 2019-04-09 + +✨ **New feature** + +- Experimental support for closures 🎉 + +💥 **Minor BC break** + +- `export()` does not throw an exception anymore when encountering a `Closure`. + To get the old behaviour back, use the `NO_CLOSURES` option. + +## [0.1.2](https://github.com/brick/varexporter/releases/tag/0.1.2) - 2019-04-08 + +🐛 **Bug fixes** + +- Static properties in custom classes were wrongly included—`unset()`—in the output + +✨ **Improvements** + +- Circular references are now detected, and throw an `ExportException` instead of erroring. + +## [0.1.1](https://github.com/brick/varexporter/releases/tag/0.1.1) - 2019-04-08 + +🐛 **Bug fixes** + +- Single-letter properties were wrongly exported using `->{'x'}` notation. + +✨ **Improvements** + +- Exception messages now contain the path (array keys / object properties) to the failure: + + > `[foo][bar][0]` Type "resource" is not supported. + +## [0.1.0](https://github.com/brick/varexporter/releases/tag/0.1.0) - 2019-04-07 + +First release. + From 18e8d96b6c4da4e5f40f7fe0a1b0b3537bac689d Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 30 Jun 2022 01:06:32 +0200 Subject: [PATCH 068/129] Rename INLINE_NUMERIC_SCALAR_ARRAY to INLINE_SCALAR_LIST --- CHANGELOG.md | 6 ++++++ README.md | 6 +++--- src/Internal/GenericExporter.php | 12 ++++++------ src/VarExporter.php | 9 +++++++-- tests/VarExporterTest.php | 6 +++--- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90bd94b..15ef2bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## UNRELEASED (0.3.7) + +🗑️ **Deprecated** + +- The `VarExporter::INLINE_NUMERIC_SCALAR_ARRAY` is deprecated, please use `INLINE_SCALAR_LIST` instead + ## [0.3.6](https://github.com/brick/varexporter/releases/tag/0.3.6) - 2022-06-15 ✨ **New feature** diff --git a/README.md b/README.md index 3d867e7..958e7c3 100644 --- a/README.md +++ b/README.md @@ -451,7 +451,7 @@ Disallows exporting any custom object using direct property access and bound clo Disallows exporting closures. -### `VarExporter::INLINE_NUMERIC_SCALAR_ARRAY` +### `VarExporter::INLINE_SCALAR_LIST` Formats numeric arrays containing only scalar values on a single line: @@ -459,7 +459,7 @@ Formats numeric arrays containing only scalar values on a single line: VarExporter::export([ 'one' => ['hello', 'world', 123, true, false, null, 7.5], 'two' => ['hello', 'world', ['one', 'two', 'three']] -], VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); +], VarExporter::INLINE_SCALAR_LIST); ``` ```php @@ -482,7 +482,7 @@ Adds a trailing comma after the last item of *non-inline* arrays: ```php VarExporter::export( ['hello', 'world', ['one', 'two', 'three']], - VarExporter::TRAILING_COMMA_IN_ARRAY | VarExporter::INLINE_NUMERIC_SCALAR_ARRAY + VarExporter::TRAILING_COMMA_IN_ARRAY | VarExporter::INLINE_SCALAR_LIST ); ``` diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 19e8cc8..efe7a56 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -51,7 +51,7 @@ final class GenericExporter * * @var bool */ - public $inlineNumericScalarArray; + public $inlineScalarList; /** * @psalm-readonly @@ -106,7 +106,7 @@ public function __construct(int $options, int $indentLevel = 0) $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); - $this->inlineNumericScalarArray = (bool) ($options & VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); + $this->inlineScalarList = (bool) ($options & VarExporter::INLINE_SCALAR_LIST); $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); $this->trailingCommaInArray = (bool) ($options & VarExporter::TRAILING_COMMA_IN_ARRAY); @@ -169,11 +169,11 @@ public function exportArray(array $array, array $path, array $parentIds) : array $result = []; $count = count($array); - $isNumeric = array_keys($array) === range(0, $count - 1); + $isList = array_keys($array) === range(0, $count - 1); $current = 0; - $inline = ($this->inlineNumericScalarArray && $isNumeric && $this->isScalarArray($array)); + $inline = ($this->inlineScalarList && $isList && $this->isScalarList($array)); foreach ($array as $key => $value) { $isLast = (++$current === $count); @@ -189,7 +189,7 @@ public function exportArray(array $array, array $path, array $parentIds) : array $prepend = ''; $append = ''; - if (! $isNumeric) { + if (! $isList) { $prepend = var_export($key, true) . ' => '; } @@ -224,7 +224,7 @@ public function exportArray(array $array, array $path, array $parentIds) : array * * @return bool */ - private function isScalarArray(array $array) : bool + private function isScalarList(array $array) : bool { foreach ($array as $value) { if ($value !== null && ! is_scalar($value)) { diff --git a/src/VarExporter.php b/src/VarExporter.php index 83b0892..f7e05db 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -48,10 +48,15 @@ final class VarExporter public const NO_CLOSURES = 1 << 6; /** - * Formats numeric arrays containing only scalar values on a single line. + * Formats lists (0-based numeric arrays) containing only scalar values on a single line. * Types considered scalar here are int, bool, float, string and null. */ - public const INLINE_NUMERIC_SCALAR_ARRAY = 1 << 7; + public const INLINE_SCALAR_LIST = 1 << 7; + + /** + * @deprecated Please use INLINE_SCALAR_LIST instead. + */ + public const INLINE_NUMERIC_SCALAR_ARRAY = self::INLINE_SCALAR_LIST; /** * Export static vars defined via `use` as variables. diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 99792b6..7bdfa30 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -182,7 +182,7 @@ public function testAddReturn(): void $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN); } - public function testInlineNumericScalarArray(): void + public function testInlineScalarList(): void { $var = [ 'one' => ['hello', 'world', 123, true, false, null, 7.5], @@ -200,7 +200,7 @@ public function testInlineNumericScalarArray(): void ] PHP; - $this->assertExportEquals($expected, $var, VarExporter::INLINE_NUMERIC_SCALAR_ARRAY); + $this->assertExportEquals($expected, $var, VarExporter::INLINE_SCALAR_LIST); } public function testTrailingCommaInArray(): void @@ -221,7 +221,7 @@ public function testTrailingCommaInArray(): void ] PHP; - $this->assertExportEquals($expected, $var, VarExporter::INLINE_NUMERIC_SCALAR_ARRAY | VarExporter::TRAILING_COMMA_IN_ARRAY); + $this->assertExportEquals($expected, $var, VarExporter::INLINE_SCALAR_LIST | VarExporter::TRAILING_COMMA_IN_ARRAY); } public function testExportDateTime(): void From d48ea790c1b9c31f2730eece020987bfd45c391c Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 30 Jun 2022 01:15:12 +0200 Subject: [PATCH 069/129] Remove buggy docblock --- src/Internal/GenericExporter.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index efe7a56..a2de531 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -74,10 +74,6 @@ final class GenericExporter */ public $indentLevel; - /** - * @param int $options - * @param int Indentation level - */ public function __construct(int $options, int $indentLevel = 0) { $this->objectExporters[] = new ObjectExporter\StdClassExporter($this); From be1fe973a7a4965814070c4bb879ffc35ad59c31 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 30 Jun 2022 01:33:50 +0200 Subject: [PATCH 070/129] Add INLINE_ARRAY option --- CHANGELOG.md | 4 ++++ README.md | 21 +++++++++++++++++++++ src/Internal/GenericExporter.php | 16 ++++++++++++++-- src/VarExporter.php | 6 ++++++ tests/VarExporterTest.php | 14 ++++++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15ef2bd..e2ea7b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## UNRELEASED (0.3.7) +✨ **New feature** + +- New option: `VarExporter::INLINE_ARRAY` + 🗑️ **Deprecated** - The `VarExporter::INLINE_NUMERIC_SCALAR_ARRAY` is deprecated, please use `INLINE_SCALAR_LIST` instead diff --git a/README.md b/README.md index 958e7c3..bc4f8c6 100644 --- a/README.md +++ b/README.md @@ -451,6 +451,25 @@ Disallows exporting any custom object using direct property access and bound clo Disallows exporting closures. +### `VarExporter::INLINE_ARRAY` + +Formats arrays on a single line: + +```php +VarExporter::export([ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => ['hello', 'world', [ + 'one', + 'two', + 'three' + ]] +], VarExporter::INLINE_ARRAY); +``` + +```php +['one' => ['hello', 'world', 123, true, false, null, 7.5], 'two' => ['hello', 'world', ['one', 'two', 'three']]] +``` + ### `VarExporter::INLINE_SCALAR_LIST` Formats numeric arrays containing only scalar values on a single line: @@ -475,6 +494,8 @@ VarExporter::export([ Types considered scalar here are `int`, `bool`, `float`, `string` and `null`. +This option is a subset of `INLINE_ARRAY`, and has no effect when `INLINE_ARRAY` is used. + ### `VarExporter::TRAILING_COMMA_IN_ARRAY` Adds a trailing comma after the last item of *non-inline* arrays: diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index a2de531..6b520a2 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -46,6 +46,13 @@ final class GenericExporter */ public $skipDynamicProperties; + /** + * @psalm-readonly + * + * @var bool + */ + public $inlineArray; + /** * @psalm-readonly * @@ -102,6 +109,7 @@ public function __construct(int $options, int $indentLevel = 0) $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); + $this->inlineArray = (bool) ($options & VarExporter::INLINE_ARRAY); $this->inlineScalarList = (bool) ($options & VarExporter::INLINE_SCALAR_LIST); $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); $this->trailingCommaInArray = (bool) ($options & VarExporter::TRAILING_COMMA_IN_ARRAY); @@ -169,7 +177,7 @@ public function exportArray(array $array, array $path, array $parentIds) : array $current = 0; - $inline = ($this->inlineScalarList && $isList && $this->isScalarList($array)); + $inline = $this->inlineArray || ($this->inlineScalarList && $isList && $this->isScalarList($array)); foreach ($array as $key => $value) { $isLast = (++$current === $count); @@ -180,7 +188,11 @@ public function exportArray(array $array, array $path, array $parentIds) : array $exported = $this->export($value, $newPath, $parentIds); if ($inline) { - $result[] = $exported[0]; + if ($isList) { + $result[] = $exported[0]; + } else { + $result[] = var_export($key, true) . ' => ' . $exported[0]; + } } else { $prepend = ''; $append = ''; diff --git a/src/VarExporter.php b/src/VarExporter.php index f7e05db..21d6e4f 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -50,6 +50,7 @@ final class VarExporter /** * Formats lists (0-based numeric arrays) containing only scalar values on a single line. * Types considered scalar here are int, bool, float, string and null. + * This option is a subset of INLINE_ARRAY, and has no effect when INLINE_ARRAY is used. */ public const INLINE_SCALAR_LIST = 1 << 7; @@ -73,6 +74,11 @@ final class VarExporter */ public const NO_ENUMS = 1 << 10; + /** + * Formats all arrays on a single line. + */ + public const INLINE_ARRAY = 1 << 11; + /** * @param mixed $var The variable to export. * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 7bdfa30..8151fb1 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -182,6 +182,20 @@ public function testAddReturn(): void $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN); } + public function testInlineArray(): void + { + $var = [ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => ['hello', 'world', [1 => 'one', 'two', 'three']] + ]; + + $expected = <<<'PHP' +['one' => ['hello', 'world', 123, true, false, null, 7.5], 'two' => ['hello', 'world', [1 => 'one', 2 => 'two', 3 => 'three']]] +PHP; + + $this->assertExportEquals($expected, $var, VarExporter::INLINE_ARRAY); + } + public function testInlineScalarList(): void { $var = [ From af6a711433e17e295f971944eb8c8bd92c5be6fa Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 30 Jun 2022 01:37:57 +0200 Subject: [PATCH 071/129] Prepare for release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ea7b5..cf88b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## UNRELEASED (0.3.7) +## [0.3.7](https://github.com/brick/varexporter/releases/tag/0.3.7) - 2022-06-30 ✨ **New feature** From 60475600025cb5d1816a48f02edd22b66ac81e95 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Tue, 1 Nov 2022 12:21:09 +0100 Subject: [PATCH 072/129] Test PHP 8.2 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7da574c..acb46e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,7 @@ jobs: - "7.4" - "8.0" - "8.1" + - "8.2" deps: - "highest" include: From fd0e05af6e9286bf1cb1fbc92911c58f5accd633 Mon Sep 17 00:00:00 2001 From: Anna Damm Date: Sun, 22 Jan 2023 00:02:19 +0100 Subject: [PATCH 073/129] [master] support public readonly properties, fixes #27 (#28) --- .../ObjectExporter/AnyObjectExporter.php | 32 +++++----- src/Internal/ObjectExporter/EnumExporter.php | 9 +-- ...icReadonlyPropertiesWithoutConstructor.php | 12 ++++ .../ReadonlyPropertiesWithConstructor.php | 15 +++++ tests/ExportObjectTest.php | 61 ++++++++++++++++++- 5 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php create mode 100644 tests/Classes/ReadonlyPropertiesWithConstructor.php diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index 090393e..7cb3b62 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -44,10 +44,10 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra $returnNewObject = ($reflectionObject->getConstructor() === null); while ($current) { - $publicProperties = []; - $nonPublicProperties = []; - $unsetPublicProperties = []; - $unsetNonPublicProperties = []; + $publicNonReadonlyProperties = []; + $nonPublicOrPublicReadonlyProperties = []; + $unsetPublicNonReadonlyProperties = []; + $unsetNonPublicOrPublicReadonlyProperties = []; foreach ($current->getProperties() as $property) { if ($property->isStatic()) { @@ -70,26 +70,26 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra if (array_key_exists($key, $objectAsArray)) { $value = $objectAsArray[$key]; - if ($property->isPublic()) { - $publicProperties[$name] = $value; + if ($property->isPublic() && !(method_exists($property, 'isReadOnly') && $property->isReadOnly())) { + $publicNonReadonlyProperties[$name] = $value; } else { - $nonPublicProperties[$name] = $value; + $nonPublicOrPublicReadonlyProperties[$name] = $value; } } else { - if ($property->isPublic()) { - $unsetPublicProperties[] = $name; + if ($property->isPublic() && !(method_exists($property, 'isReadOnly') && $property->isReadOnly())) { + $unsetPublicNonReadonlyProperties[] = $name; } else { - $unsetNonPublicProperties[] = $name; + $unsetNonPublicOrPublicReadonlyProperties[] = $name; } } $returnNewObject = false; } - if ($publicProperties || $unsetPublicProperties) { + if ($publicNonReadonlyProperties || $unsetPublicNonReadonlyProperties) { $lines[] = ''; - foreach ($publicProperties as $name => $value) { + foreach ($publicNonReadonlyProperties as $name => $value) { /** @psalm-suppress RedundantCast See: https://github.com/vimeo/psalm/issues/4891 */ $name = (string) $name; @@ -104,19 +104,19 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra $lines = array_merge($lines, $exportedValue); } - foreach ($unsetPublicProperties as $name) { + foreach ($unsetPublicNonReadonlyProperties as $name) { $lines[] = 'unset($object->' . $this->escapePropName($name) . ');'; } } - if ($nonPublicProperties || $unsetNonPublicProperties) { + if ($nonPublicOrPublicReadonlyProperties || $unsetNonPublicOrPublicReadonlyProperties) { $closureLines = []; if ($this->exporter->addTypeHints) { $closureLines[] = '/** @var \\' . $current->getName() . ' $this */'; } - foreach ($nonPublicProperties as $name => $value) { + foreach ($nonPublicOrPublicReadonlyProperties as $name => $value) { $newPath = $path; $newPath[] = $name; @@ -128,7 +128,7 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra $closureLines = array_merge($closureLines, $exportedValue); } - foreach ($unsetNonPublicProperties as $name) { + foreach ($unsetNonPublicOrPublicReadonlyProperties as $name) { $closureLines[] = 'unset($this->' . $this->escapePropName($name) . ');'; } diff --git a/src/Internal/ObjectExporter/EnumExporter.php b/src/Internal/ObjectExporter/EnumExporter.php index 6c47358..dfec28f 100644 --- a/src/Internal/ObjectExporter/EnumExporter.php +++ b/src/Internal/ObjectExporter/EnumExporter.php @@ -18,16 +18,11 @@ class EnumExporter extends ObjectExporter * {@inheritDoc} * * See: https://github.com/vimeo/psalm/pull/8117 - * @psalm-suppress MixedInferredReturnType - * @psalm-suppress MixedReturnStatement + * @psalm-suppress RedundantCondition */ public function supports(\ReflectionObject $reflectionObject) : bool { - if (! method_exists($reflectionObject, 'isEnum')) { - return false; - } - - return $reflectionObject->isEnum(); + return method_exists($reflectionObject, 'isEnum') && $reflectionObject->isEnum(); } /** diff --git a/tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php b/tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php new file mode 100644 index 0000000..46ae1d5 --- /dev/null +++ b/tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php @@ -0,0 +1,12 @@ +assertExportEquals($expected, $object); } + /** + * @requires PHP 8.1 + */ + public function testExportClassWithReadonlyPublicPropertiesAndConstructor(): void + { + $object = new ReadonlyPropertiesWithConstructor('public readonly', 'private readonly', 'public'); + + $expected = <<<'PHP' +(static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor::class); + $object = $class->newInstanceWithoutConstructor(); + + $object->baz = 'public'; + + (function() { + $this->foo = 'public readonly'; + $this->bar = 'private readonly'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor::class)(); + + return $object; +})() +PHP; + + $this->assertExportEquals($expected, $object); + } + + /** + * @requires PHP 8.1 + */ + public function testExportClassWithStateAndReadonlyPublicProperties(): void + { + $object = new PublicReadonlyPropertiesWithoutConstructor(); + + (function () { + $this->foo = 'foo'; + })->bindTo($object, PublicReadonlyPropertiesWithoutConstructor::class)(); + + $expected = <<<'PHP' +(static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor; + + unset($object->baz); + + (function() { + $this->foo = 'foo'; + unset($this->bar); + })->bindTo($object, \Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor::class)(); + + return $object; +})() +PHP; + + $this->assertExportEquals($expected, $object); + } + public function testExportClassWithSerializeMagicMethods(): void { $object = new SerializeMagicMethods; From 482749e648cad1047f7d28e86bea810244edcbbb Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 22 Jan 2023 00:05:38 +0100 Subject: [PATCH 074/129] Prepare for release --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf88b66..f636ce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.3.8](https://github.com/brick/varexporter/releases/tag/0.3.8) - 2023-01-22 + +✨ **New feature** + +- Support for PHP 8.1 `readonly` properties (#27, #28) + +Thanks @AnnaDamm! + ## [0.3.7](https://github.com/brick/varexporter/releases/tag/0.3.7) - 2022-06-30 ✨ **New feature** From 946e00ee16d32a3dd181f8bf13633079e9c3960d Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 23 Feb 2023 22:17:26 +0100 Subject: [PATCH 075/129] Upgrade to Psalm 5.7.5 --- composer.json | 2 +- psalm.xml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 916985d..e397237 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "4.23.0" + "vimeo/psalm": "5.7.5" }, "autoload": { "psr-4": { diff --git a/psalm.xml b/psalm.xml index 3240886..f19428c 100644 --- a/psalm.xml +++ b/psalm.xml @@ -5,6 +5,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" + findUnusedBaselineEntry="true" + findUnusedPsalmSuppress="true" + findUnusedCode="false" > From abc32622750c8330b777162ef89b1fa401a98b30 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 22:49:48 +0200 Subject: [PATCH 076/129] Upgrade to Psalm 5.15.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e397237..a821c26 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "5.7.5" + "vimeo/psalm": "5.15.0" }, "autoload": { "psr-4": { From bbbf580a3e492fbde2dffdec39aa7c0346c48cbb Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 22:50:07 +0200 Subject: [PATCH 077/129] Test on PHP 8.3 --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acb46e8..7d1ad39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,7 @@ jobs: - "8.0" - "8.1" - "8.2" + - "8.3" deps: - "highest" include: From f9f23ad74311d5dbf96050ae2ccb44b827eeb35c Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 22:58:16 +0200 Subject: [PATCH 078/129] Require PHP 7.4 --- .github/workflows/ci.yml | 4 +--- CHANGELOG.md | 4 ++++ README.md | 4 +++- composer.json | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d1ad39..5979f50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,6 @@ jobs: strategy: matrix: php-version: - - "7.2" - - "7.3" - "7.4" - "8.0" - "8.1" @@ -41,7 +39,7 @@ jobs: deps: - "highest" include: - - php-version: "7.2" + - php-version: "7.4" deps: "lowest" steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index f636ce3..76fd06e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## UNRELEASED (0.4.0) + +Minimum PHP version is now `7.4`. No breaking changes. + ## [0.3.8](https://github.com/brick/varexporter/releases/tag/0.3.8) - 2023-01-22 ✨ **New feature** diff --git a/README.md b/README.md index bc4f8c6..523d2cf 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,9 @@ composer require brick/varexporter ### Requirements -This library requires PHP 7.2 or later. +This library requires PHP 7.4 or later. + +For PHP 7.2 & 7.3 compatibility, you can use version `0.3`. For PHP 7.1, you can use version `0.2`. Note that [these PHP versions are EOL](http://php.net/supported-versions.php) and not supported anymore. If you're still using one of these PHP versions, you should consider upgrading as soon as possible. ### Project status & release process diff --git a/composer.json b/composer.json index a821c26..b26fe4a 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "license": "MIT", "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "nikic/php-parser": "^4.0" }, "require-dev": { From 501613f501c1ac3fddf44d13cd5707cbea64cacd Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 23:03:00 +0200 Subject: [PATCH 079/129] Typed properties --- src/Internal/GenericExporter.php | 2 +- src/Internal/ObjectExporter/ClosureExporter.php | 5 +---- .../ObjectExporter/ClosureExporter/PrettyPrinter.php | 5 +---- tests/Classes/PublicAndPrivateProperties.php | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 6b520a2..6787b3c 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -30,7 +30,7 @@ final class GenericExporter * * @var array> */ - private $visitedObjects = []; + private array $visitedObjects = []; /** * @psalm-readonly diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index ea98891..03ce908 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -23,10 +23,7 @@ */ class ClosureExporter extends ObjectExporter { - /** - * @var Parser|null - */ - private $parser; + private ?Parser $parser = null; /** * {@inheritDoc} diff --git a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php index 4010810..5f1430d 100644 --- a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php +++ b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php @@ -13,10 +13,7 @@ */ final class PrettyPrinter extends Standard { - /** - * @var int - */ - private $varExporterNestingLevel = 0; + private int $varExporterNestingLevel = 0; /** * @param int $level diff --git a/tests/Classes/PublicAndPrivateProperties.php b/tests/Classes/PublicAndPrivateProperties.php index 3954f14..327d35c 100644 --- a/tests/Classes/PublicAndPrivateProperties.php +++ b/tests/Classes/PublicAndPrivateProperties.php @@ -6,7 +6,7 @@ class PublicAndPrivateProperties extends PublicPropertiesOnly { - private $baz = 'defaultValue'; + private string $baz = 'defaultValue'; protected function setBaz($baz) : void { From 18d9148b00ad22a3531891a92ec76f20682652e4 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 23:03:25 +0200 Subject: [PATCH 080/129] Arrow functions --- src/Internal/ObjectExporter/ClosureExporter.php | 8 ++++---- src/VarExporter.php | 7 ++++--- tests/AbstractTestCase.php | 4 +--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 03ce908..5ecdf10 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -147,10 +147,10 @@ private function getClosure( int $line, array $path ) : Node\Expr\Closure { - $finder = new FindingVisitor(function(Node $node) use ($line) : bool { - return ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) - && $node->getStartLine() === $line; - }); + $finder = new FindingVisitor( + fn(Node $node): bool => ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) + && $node->getStartLine() === $line + ); $traverser = new NodeTraverser(); $traverser->addVisitor($finder); diff --git a/src/VarExporter.php b/src/VarExporter.php index 21d6e4f..13fc356 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -98,9 +98,10 @@ public static function export($var, int $options = 0, int $indentLevel = 0) : st $export = implode(PHP_EOL, $lines); } else { $firstLine = array_shift($lines); - $lines = array_map(function ($line) use ($indentLevel) { - return str_repeat(' ', $indentLevel) . $line; - }, $lines); + $lines = array_map( + fn($line) => str_repeat(' ', $indentLevel) . $line, + $lines, + ); $export = $firstLine . PHP_EOL . implode(PHP_EOL, $lines); } diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index edcef7a..82e77a1 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -63,9 +63,7 @@ public function assertExportEquals(string $expected, $var, int $options = 0) : v */ public function assertExportThrows(string $expectedMessage, $var, int $options = 0) : void { - $expectedMessageRegExp = '/' . implode('.*', array_map(function(string $str) { - return preg_quote($str, '/'); - }, explode('*', $expectedMessage))) . '/'; + $expectedMessageRegExp = '/' . implode('.*', array_map(fn(string $str) => preg_quote($str, '/'), explode('*', $expectedMessage))) . '/'; $this->expectException(ExportException::class); $this->expectExceptionMessageMatches($expectedMessageRegExp); From c413afad1acd14cc37277f7798abe669845baf14 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 23:07:37 +0200 Subject: [PATCH 081/129] Prepare for release --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76fd06e..847cae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## UNRELEASED (0.4.0) +## [0.4.0](https://github.com/brick/varexporter/releases/tag/0.4.0) - 2023-09-01 Minimum PHP version is now `7.4`. No breaking changes. diff --git a/README.md b/README.md index 523d2cf..69daeb9 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ The current releases are numbered `0.x.y`. When a non-breaking change is introdu **When a breaking change is introduced, a new `0.x` version cycle is always started.** -It is therefore safe to lock your project to a given release cycle, such as `0.3.*`. +It is therefore safe to lock your project to a given release cycle, such as `0.4.*`. If you need to upgrade to a newer release cycle, check the [release history](https://github.com/brick/varexporter/releases) for a list of changes introduced by each further `0.x.0` version. From 05b38f4cc1f2b4ab846c1d46d9733304800d8167 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 23:10:07 +0200 Subject: [PATCH 082/129] Remove outdated PHP < 7.3 docs --- README.md | 57 ------------------------------------------------------- 1 file changed, 57 deletions(-) diff --git a/README.md b/README.md index 69daeb9..9d162b6 100644 --- a/README.md +++ b/README.md @@ -97,63 +97,6 @@ array ( Note: unlike `var_export()`, `export()` always returns the exported variable, and never outputs it. -## Exporting stdClass objects - -You come across a `stdClass` object every time you cast an array to an object, or use `json_decode()` with the second argument set to `false` (which is the default). - -While the output of `var_export()` for `stdClass` is syntactically valid PHP code: - -```php -var_export(json_decode(' - { - "foo": "bar", - "baz": { - "hello": "world" - } - } -')); -``` - -```php -stdClass::__set_state(array( - 'foo' => 'bar', - 'baz' => - stdClass::__set_state(array( - 'hello' => 'world', - )), -)) -``` - -it is totally useless as it assumes that `stdClass` has a static `__set_state()` method, when it doesn't: - -> Error: Call to undefined method stdClass::__set_state() - -### What does `VarExporter` do instead? - -It outputs an array to object cast, which is syntactically valid, readable **and** executable: - -```php -echo VarExporter::export(json_decode(' - { - "foo": "bar", - "baz": { - "hello": "world" - } - } -')); -``` - -```php -(object) [ - 'foo' => 'bar', - 'baz' => (object) [ - 'hello' => 'world' - ] -] -``` - -**Note: since PHP 7.3, `var_export()` now exports an array to object cast like `VarExporter::export()` does.** - ## Exporting custom objects As we've seen above, `var_export()` assumes that every object has a static [__set_state()](https://www.php.net/manual/en/language.oop5.magic.php#object.set-state) method that takes an associative array of property names to values, and returns a object. From 847ece7018146c3fff2ecb7c0d2e216e61b4bd1b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 23:13:53 +0200 Subject: [PATCH 083/129] Remove outdated PHP < 7.3 docs, again --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9d162b6..f6d3ccd 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ It is particularly useful to store data that can be cached by OPCache, just like But it also suffers from several drawbacks: -- It outputs invalid PHP code for `stdClass` objects, using `stdClass::__set_state()` which doesn't exist (PHP < 7.3) - It cannot export custom objects that do not implement `__set_state()`, and `__set_state()` does not play well with private properties in parent classes, which makes the implementation tedious - It does not support closures From 73a720847ce442872b7448b0c632d5cf0f565d3c Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 1 Sep 2023 23:15:40 +0200 Subject: [PATCH 084/129] Small README fixes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6d3ccd..31d19d4 100644 --- a/README.md +++ b/README.md @@ -98,9 +98,9 @@ Note: unlike `var_export()`, `export()` always returns the exported variable, an ## Exporting custom objects -As we've seen above, `var_export()` assumes that every object has a static [__set_state()](https://www.php.net/manual/en/language.oop5.magic.php#object.set-state) method that takes an associative array of property names to values, and returns a object. +`var_export()` assumes that every object has a static [__set_state()](https://www.php.net/manual/en/language.oop5.magic.php#object.set-state) method that takes an associative array of property names to values, and returns a object. -This means that if you want to export an instance of a class outside of your control, you're screwed up. This also means that you have to write boilerplate code for your classes, that looks like: +This means that if you want to export an instance of a class outside your control, you're screwed up. This also means that you have to write boilerplate code for your classes, that looks like: ```php class Foo From 949c7328695348887b49b1567537fc2085db5585 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sun, 4 Feb 2024 13:54:14 +0100 Subject: [PATCH 085/129] Upgrade Psalm to 5.21.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b26fe4a..d610968 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "5.15.0" + "vimeo/psalm": "5.21.1" }, "autoload": { "psr-4": { From 2d71f130bb26faaf9d80661d816cb9688e27b953 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 25 Apr 2024 14:29:53 +0200 Subject: [PATCH 086/129] Env vars for Psalm & coverage PHP versions, use actions/checkout@v4 --- .github/workflows/ci.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5979f50..52e81e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,10 @@ on: push: pull_request: +env: + PSALM_PHP_VERSION: "8.3" + COVERAGE_PHP_VERSION: "8.3" + jobs: psalm: name: Psalm @@ -11,12 +15,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: "8.1" + php-version: ${{ env.PSALM_PHP_VERSION }} - name: Install composer dependencies uses: "ramsey/composer-install@v1" @@ -44,7 +48,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -59,16 +63,16 @@ jobs: - name: Run PHPUnit run: vendor/bin/phpunit - if: ${{ matrix.php-version != '8.1' }} + if: ${{ matrix.php-version != env.COVERAGE_PHP_VERSION }} - name: Run PHPUnit with coverage run: | mkdir -p build/logs vendor/bin/phpunit --coverage-clover build/logs/clover.xml - if: ${{ matrix.php-version == '8.1' }} + if: ${{ matrix.php-version == env.COVERAGE_PHP_VERSION }} - name: Upload coverage report to Coveralls run: vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: ${{ matrix.php-version == '8.1' }} + if: ${{ matrix.php-version == env.COVERAGE_PHP_VERSION }} From 798c0ce0ffc63f80fa8e1889e477975b3ba5a10b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 25 Apr 2024 15:12:44 +0200 Subject: [PATCH 087/129] Use ramsey/composer-install@v3 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52e81e3..3248e37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: php-version: ${{ env.PSALM_PHP_VERSION }} - name: Install composer dependencies - uses: "ramsey/composer-install@v1" + uses: ramsey/composer-install@v3 - name: Run Psalm run: vendor/bin/psalm --show-info=false --find-unused-psalm-suppress --no-progress @@ -57,7 +57,7 @@ jobs: coverage: pcov - name: Install composer dependencies - uses: "ramsey/composer-install@v1" + uses: ramsey/composer-install@v3 with: dependency-versions: ${{ matrix.deps }} From 069caaf34587829a79bc5bc5b76be80f0d356a1d Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 3 May 2024 01:33:05 +0200 Subject: [PATCH 088/129] Use fail-fast: false --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3248e37..44489e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: php-version: - "7.4" From d46e694f303e30ff799b7601aff97275a6d4eb00 Mon Sep 17 00:00:00 2001 From: Witold Wasiczko Date: Wed, 3 Apr 2024 11:10:02 +0200 Subject: [PATCH 089/129] Use psalm as phar --- .github/workflows/ci.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44489e0..093b739 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: uses: ramsey/composer-install@v3 - name: Run Psalm - run: vendor/bin/psalm --show-info=false --find-unused-psalm-suppress --no-progress + run: vendor/bin/psalm.phar --show-info=false --find-unused-psalm-suppress --no-progress phpunit: name: PHPUnit diff --git a/composer.json b/composer.json index d610968..8848ec1 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^8.5 || ^9.0", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "5.21.1" + "psalm/phar": "5.21.1" }, "autoload": { "psr-4": { From bc6fe0bcaded225dc0fe5ccfaf0a8261a381be73 Mon Sep 17 00:00:00 2001 From: Witold Wasiczko Date: Wed, 3 Apr 2024 11:12:23 +0200 Subject: [PATCH 090/129] Update phpunit --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8848ec1..5153bd8 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "nikic/php-parser": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.0", + "phpunit/phpunit": "^9.3", "php-coveralls/php-coveralls": "^2.2", "psalm/phar": "5.21.1" }, From 4b8fe1c8b40ac6ccc7dc8c3a08093a71ecd962f9 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 10 May 2024 19:00:05 +0200 Subject: [PATCH 091/129] Upgrade to nikic/php-parser v5 --- CHANGELOG.md | 7 +++++++ composer.json | 2 +- .../ObjectExporter/ClosureExporter.php | 3 ++- tests/ExportClosureTest.php | 18 +++++++++--------- tests/VarExporterTest.php | 12 ++++++------ 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 847cae6..04e54d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## UNRELEASED (0.5.0) + +✨ **Compatibility** + +- Added compatibility with `nikic/php-parser` `5.x` +- Removed compatibility with `nikic/php-parser` `4.x` + ## [0.4.0](https://github.com/brick/varexporter/releases/tag/0.4.0) - 2023-09-01 Minimum PHP version is now `7.4`. No breaking changes. diff --git a/composer.json b/composer.json index 5153bd8..24126ea 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "license": "MIT", "require": { "php": "^7.4 || ^8.0", - "nikic/php-parser": "^4.0" + "nikic/php-parser": "^5.0" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 5ecdf10..d1883b1 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -14,6 +14,7 @@ use PhpParser\NodeVisitor\NameResolver; use PhpParser\Parser; use PhpParser\ParserFactory; +use PhpParser\PhpVersion; use ReflectionFunction; /** @@ -67,7 +68,7 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra private function getParser() { if ($this->parser === null) { - $this->parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); + $this->parser = (new ParserFactory)->createForVersion(PhpVersion::fromComponents(7, 4)); } return $this->parser; diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index c00ddb7..6c9464d 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -61,7 +61,7 @@ public function testExportNestedComplexClosure(): void [ (object) [ 'callback' => function () { - return function (\Brick\VarExporter\Tests\Classes\PublicPropertiesOnly $a, int $b, string &$c, string ...$d) : ?string { + return function (\Brick\VarExporter\Tests\Classes\PublicPropertiesOnly $a, int $b, string &$c, string ...$d): ?string { $a->foo += $b; $c = $a->bar; $a->bar = implode('', $d); @@ -91,8 +91,8 @@ public function testExportNamespacedCode(): void }; $expected = <<<'PHP' -function (\Brick\VarExporter\Tests\Classes\SetState $a) : array { - return ['callback' => function (\Brick\VarExporter\Tests\Classes\SetState $a) : \Brick\VarExporter\Tests\Classes\NoProperties { +function (\Brick\VarExporter\Tests\Classes\SetState $a): array { + return ['callback' => function (\Brick\VarExporter\Tests\Classes\SetState $a): \Brick\VarExporter\Tests\Classes\NoProperties { strlen(PHP_VERSION); \Brick\VarExporter\Dummy\Functions\imported_function(\Brick\VarExporter\Dummy\Constants\IMPORTED_CONSTANT); \Brick\VarExporter\Dummy\Functions\explicitly_namespaced_function(\Brick\VarExporter\Dummy\Constants\EXPLICITLY_NAMESPACED_CONSTANT); @@ -128,13 +128,13 @@ public function testExportClosureWithStringsContainingLikeBreaks(): void $a = 'Hello, World!'; $b = << Date: Fri, 10 May 2024 19:07:38 +0200 Subject: [PATCH 092/129] Remove deprecated INLINE_NUMERIC_SCALAR_ARRAY --- CHANGELOG.md | 4 ++++ src/VarExporter.php | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04e54d3..7561f32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - Added compatibility with `nikic/php-parser` `5.x` - Removed compatibility with `nikic/php-parser` `4.x` +💥 **BC breaks** + +- deprecated constant `VarExporter::INLINE_NUMERIC_SCALAR_ARRAY` has been removed, please use `INLINE_SCALAR_LIST` instead + ## [0.4.0](https://github.com/brick/varexporter/releases/tag/0.4.0) - 2023-09-01 Minimum PHP version is now `7.4`. No breaking changes. diff --git a/src/VarExporter.php b/src/VarExporter.php index 13fc356..138c431 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -54,11 +54,6 @@ final class VarExporter */ public const INLINE_SCALAR_LIST = 1 << 7; - /** - * @deprecated Please use INLINE_SCALAR_LIST instead. - */ - public const INLINE_NUMERIC_SCALAR_ARRAY = self::INLINE_SCALAR_LIST; - /** * Export static vars defined via `use` as variables. */ From 84b2a7a91f69aa5d079aec5a0a7256ebf2dceb6b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 10 May 2024 19:14:55 +0200 Subject: [PATCH 093/129] Prepare for release --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7561f32..dfee8bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## UNRELEASED (0.5.0) +## [0.5.0](https://github.com/brick/varexporter/releases/tag/0.5.0) - 2024-05-10 ✨ **Compatibility** diff --git a/README.md b/README.md index 31d19d4..6f8ce4a 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The current releases are numbered `0.x.y`. When a non-breaking change is introdu **When a breaking change is introduced, a new `0.x` version cycle is always started.** -It is therefore safe to lock your project to a given release cycle, such as `0.4.*`. +It is therefore safe to lock your project to a given release cycle, such as `0.5.*`. If you need to upgrade to a newer release cycle, check the [release history](https://github.com/brick/varexporter/releases) for a list of changes introduced by each further `0.x.0` version. From 478a9d96ded64ae54fc3357d7616397aa787a70b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 24 May 2024 19:47:24 +0200 Subject: [PATCH 094/129] Remove pre-PHP 7.4 wording --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f8ce4a..ea5b30a 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ It determines the most appropriate method to export your object, in this order: You can disable exporting objects this way, even if they implement `__set_state()`, using the [`NO_SET_STATE`](#varexporterno_set_state) option. -- If your class has `__serialize()` and `__unserialize()` methods ([introduced in PHP 7.4](https://wiki.php.net/rfc/custom_object_serialization), but this library accepts them in previous versions of PHP!), `VarExporter` uses the output of `__serialize()` to export the object, and gives it as input to `__unserialize()` to reconstruct the object: +- If your class has `__serialize()` and `__unserialize()` methods, `VarExporter` uses the output of `__serialize()` to export the object, and gives it as input to `__unserialize()` to reconstruct the object: ```php (static function() { From 78b155d71b46ec89096dcb5fd8ec87c37e3d4072 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 10:33:46 +0100 Subject: [PATCH 095/129] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ea5b30a..bb32db2 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Note: unlike `var_export()`, `export()` always returns the exported variable, an `var_export()` assumes that every object has a static [__set_state()](https://www.php.net/manual/en/language.oop5.magic.php#object.set-state) method that takes an associative array of property names to values, and returns a object. -This means that if you want to export an instance of a class outside your control, you're screwed up. This also means that you have to write boilerplate code for your classes, that looks like: +This means that if you want to export an instance of a class outside your control, you're out of luck. This also means that you have to write boilerplate code for your classes, that looks like: ```php class Foo @@ -174,7 +174,7 @@ It determines the most appropriate method to export your object, in this order: })() ``` - This method is recommended for exporting complex custom objects: it is forward compatible with the new serialization mechanism introduced in PHP 7.4, flexible, safe, and composes very well under inheritance. + This method is recommended for exporting complex custom objects: it is compatible with the new serialization mechanism introduced in PHP 7.4, flexible, safe, and composes very well under inheritance. If for any reason you do not want to export objects that implement `__serialize()` and `__unserialize()` using this method, you can opt out by using the [`NO_SERIALIZE`](#varexporterno_serialize) option. @@ -309,7 +309,7 @@ echo VarExporter::export([ ### Arrow functions -PHP supports shorthand syntax for closures (since PHP 7.4), also known as arrow functions. `VarExporter` will export these as normal closures. +PHP supports shorthand syntax for closures, also known as arrow functions. `VarExporter` will export these as normal closures. Arrow functions can implicitly use variables from the context they've been defined in. If any context variable is used in the arrow function, `VarExporter` will throw an `ExportException` unless the [`CLOSURE_SNAPSHOT_USES`](#varexporterclosure_snapshot_uses) option is used. From 430e561439d38e46e50fa3a26b26d477beb4ac4a Mon Sep 17 00:00:00 2001 From: Maddin <9324423+reinfi@users.noreply.github.com> Date: Thu, 20 Feb 2025 12:09:21 +0100 Subject: [PATCH 096/129] Drop php version support (#39) --- .github/workflows/ci.yml | 4 +--- README.md | 4 ++-- composer.json | 2 +- src/Internal/ObjectExporter/SetStateExporter.php | 2 -- tests/ExportClosureTest.php | 12 ------------ 5 files changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 093b739..54496e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,15 +36,13 @@ jobs: fail-fast: false matrix: php-version: - - "7.4" - - "8.0" - "8.1" - "8.2" - "8.3" deps: - "highest" include: - - php-version: "7.4" + - php-version: "8.1" deps: "lowest" steps: diff --git a/README.md b/README.md index bb32db2..8c8927e 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,9 @@ composer require brick/varexporter ### Requirements -This library requires PHP 7.4 or later. +This library requires PHP 8.1 or later. -For PHP 7.2 & 7.3 compatibility, you can use version `0.3`. For PHP 7.1, you can use version `0.2`. Note that [these PHP versions are EOL](http://php.net/supported-versions.php) and not supported anymore. If you're still using one of these PHP versions, you should consider upgrading as soon as possible. +For PHP 7.4, you can use version `0.5`. For PHP 7.2 & 7.3 compatibility, you can use version `0.3`. Note that [these PHP versions are EOL](http://php.net/supported-versions.php) and not supported anymore. If you're still using one of these PHP versions, you should consider upgrading as soon as possible. ### Project status & release process diff --git a/composer.json b/composer.json index 24126ea..8a0a382 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "license": "MIT", "require": { - "php": "^7.4 || ^8.0", + "php": "^8.1", "nikic/php-parser": "^5.0" }, "require-dev": { diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index a464b99..cfa910e 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -76,8 +76,6 @@ private function getObjectVars(object $object, array $path) : array $name = substr($name, $pos + 1); } - assert($name !== false); - if (array_key_exists($name, $result)) { $className = get_class($object); diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index 6c9464d..95c6194 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -206,10 +206,6 @@ public function testExportClosureWithUseClosure(): void public function testExportArrowFunction(): void { - if (version_compare(PHP_VERSION, '7.4.0') < 0) { - $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); - } - $var = [fn ($planet) => 'hello ' . $planet]; // Wrapping in array for valid syntax PHP <7.4 $expected = <<<'PHP' @@ -226,10 +222,6 @@ function ($planet) { public function testExportArrowFunctionWithContext(): void { - if (version_compare(PHP_VERSION, '7.4.0') < 0) { - $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); - } - $greet = 'hello'; $var = [fn ($planet) => $greet . ' ' . $planet]; @@ -243,10 +235,6 @@ public function testExportArrowFunctionWithContext(): void public function testExportArrowFunctionWithContextVarAsVar(): void { - if (version_compare(PHP_VERSION, '7.4.0') < 0) { - $this->markTestSkipped("Arrow functions aren't supported in PHP " . PHP_VERSION); - } - $greet = 'hello'; $var = [fn ($planet) => $greet . ' ' . $planet]; From 759107107ca7ac736d3d82910ce4aae38b6b999a Mon Sep 17 00:00:00 2001 From: Maddin <9324423+reinfi@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:35:38 +0100 Subject: [PATCH 097/129] Use current php version parser (#38) --- .../ObjectExporter/ClosureExporter.php | 2 +- tests/ExportClosureTest.php | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index d1883b1..06bc0f4 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -68,7 +68,7 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra private function getParser() { if ($this->parser === null) { - $this->parser = (new ParserFactory)->createForVersion(PhpVersion::fromComponents(7, 4)); + $this->parser = (new ParserFactory)->createForHostVersion(); } return $this->parser; diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index 95c6194..2bba287 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -4,6 +4,7 @@ namespace Brick\VarExporter\Tests; +use Brick\VarExporter\Tests\Classes\Enum; use Brick\VarExporter\Tests\Classes\NoProperties; use Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; use Brick\VarExporter\Tests\Classes\SetState; @@ -252,6 +253,31 @@ function ($planet) { $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); } + public function testExportEnumMatchFunction(): void + { + $var = [ + (object) [ + 'callback' => static fn (Enum $enum): string => match ($enum) { + Enum::TEST => 'foo', + } + ] + ]; + + $expected = <<<'PHP' +[ + (object) [ + 'callback' => function (\Brick\VarExporter\Tests\Classes\Enum $enum): string { + return match ($enum) { + \Brick\VarExporter\Tests\Classes\Enum::TEST => 'foo', + }; + } + ] +] +PHP; + + $this->assertExportEquals($expected, $var); + } + public function testExportClosureDefinedInEval(): void { $var = eval(<< Date: Thu, 20 Feb 2025 12:12:01 +0100 Subject: [PATCH 098/129] Test on PHP 8.4 --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54496e7..a450bc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,8 @@ on: pull_request: env: - PSALM_PHP_VERSION: "8.3" - COVERAGE_PHP_VERSION: "8.3" + PSALM_PHP_VERSION: "8.4" + COVERAGE_PHP_VERSION: "8.4" jobs: psalm: @@ -39,6 +39,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" deps: - "highest" include: From 10112bd4e992d8d7da17e4ad7a16aedefd1923fd Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 12:18:41 +0100 Subject: [PATCH 099/129] Update Psalm to 6.8.4 --- .github/workflows/ci.yml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a450bc2..72d70ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: uses: ramsey/composer-install@v3 - name: Run Psalm - run: vendor/bin/psalm.phar --show-info=false --find-unused-psalm-suppress --no-progress + run: vendor/bin/psalm --show-info=false --no-progress phpunit: name: PHPUnit diff --git a/composer.json b/composer.json index 8a0a382..e2f2cdd 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^9.3", "php-coveralls/php-coveralls": "^2.2", - "psalm/phar": "5.21.1" + "vimeo/psalm": "6.8.4" }, "autoload": { "psr-4": { From b9b2b8c16f4e32c4dde4b6e1f4b0bc38c4078c97 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 12:23:03 +0100 Subject: [PATCH 100/129] #[Override] attributes --- src/Internal/ObjectExporter/AnyObjectExporter.php | 8 +++----- src/Internal/ObjectExporter/ClosureExporter.php | 9 +++------ .../ObjectExporter/ClosureExporter/PrettyPrinter.php | 5 ++--- src/Internal/ObjectExporter/EnumExporter.php | 8 +++----- src/Internal/ObjectExporter/InternalClassExporter.php | 9 +++------ src/Internal/ObjectExporter/SerializeExporter.php | 9 +++------ src/Internal/ObjectExporter/SetStateExporter.php | 9 +++------ src/Internal/ObjectExporter/StdClassExporter.php | 9 +++------ 8 files changed, 23 insertions(+), 43 deletions(-) diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index 7cb3b62..ec648fb 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -5,6 +5,7 @@ namespace Brick\VarExporter\Internal\ObjectExporter; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Handles any class through direct property access and bound closures. @@ -16,19 +17,16 @@ */ class AnyObjectExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return true; } /** - * {@inheritDoc} - * * @psalm-suppress MixedAssignment */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $lines = $this->getCreateObjectCode($reflectionObject); diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 06bc0f4..9c0074a 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -7,6 +7,7 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; use Closure; +use Override; use PhpParser\Error; use PhpParser\Node; use PhpParser\NodeTraverser; @@ -26,17 +27,13 @@ class ClosureExporter extends ObjectExporter { private ?Parser $parser = null; - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return $reflectionObject->getName() === \Closure::class; } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { assert($object instanceof Closure); diff --git a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php index 5f1430d..67773b2 100644 --- a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php +++ b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php @@ -4,6 +4,7 @@ namespace Brick\VarExporter\Internal\ObjectExporter\ClosureExporter; +use Override; use PhpParser\PrettyPrinter\Standard; /** @@ -25,9 +26,7 @@ public function setVarExporterNestingLevel(int $level) : void $this->varExporterNestingLevel = $level; } - /** - * {@inheritDoc} - */ + #[Override] protected function resetState() : void { parent::resetState(); diff --git a/src/Internal/ObjectExporter/EnumExporter.php b/src/Internal/ObjectExporter/EnumExporter.php index dfec28f..c833fae 100644 --- a/src/Internal/ObjectExporter/EnumExporter.php +++ b/src/Internal/ObjectExporter/EnumExporter.php @@ -5,6 +5,7 @@ namespace Brick\VarExporter\Internal\ObjectExporter; use Brick\VarExporter\Internal\ObjectExporter; +use Override; use UnitEnum; /** @@ -15,19 +16,16 @@ class EnumExporter extends ObjectExporter { /** - * {@inheritDoc} - * * See: https://github.com/vimeo/psalm/pull/8117 * @psalm-suppress RedundantCondition */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return method_exists($reflectionObject, 'isEnum') && $reflectionObject->isEnum(); } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { assert($object instanceof UnitEnum); diff --git a/src/Internal/ObjectExporter/InternalClassExporter.php b/src/Internal/ObjectExporter/InternalClassExporter.php index e0adbd1..bb00d7c 100644 --- a/src/Internal/ObjectExporter/InternalClassExporter.php +++ b/src/Internal/ObjectExporter/InternalClassExporter.php @@ -6,6 +6,7 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Throws on internal classes. @@ -14,17 +15,13 @@ */ class InternalClassExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return $reflectionObject->isInternal(); } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $className = $reflectionObject->getName(); diff --git a/src/Internal/ObjectExporter/SerializeExporter.php b/src/Internal/ObjectExporter/SerializeExporter.php index 604a104..a03954d 100644 --- a/src/Internal/ObjectExporter/SerializeExporter.php +++ b/src/Internal/ObjectExporter/SerializeExporter.php @@ -5,6 +5,7 @@ namespace Brick\VarExporter\Internal\ObjectExporter; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Handles instances of classes with __serialize() and __unserialize() methods. @@ -13,18 +14,14 @@ */ class SerializeExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return $reflectionObject->hasMethod('__serialize') && $reflectionObject->hasMethod('__unserialize'); } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $lines = $this->getCreateObjectCode($reflectionObject); diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index cfa910e..67f9c9f 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -6,6 +6,7 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Handles instances of classes with a __set_state() method. @@ -14,9 +15,7 @@ */ class SetStateExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { if ($reflectionObject->hasMethod('__set_state')) { @@ -28,9 +27,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool return false; } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $className = $reflectionObject->getName(); diff --git a/src/Internal/ObjectExporter/StdClassExporter.php b/src/Internal/ObjectExporter/StdClassExporter.php index ac958f1..0c8d4b3 100644 --- a/src/Internal/ObjectExporter/StdClassExporter.php +++ b/src/Internal/ObjectExporter/StdClassExporter.php @@ -5,6 +5,7 @@ namespace Brick\VarExporter\Internal\ObjectExporter; use Brick\VarExporter\Internal\ObjectExporter; +use Override; /** * Handles stdClass objects. @@ -13,17 +14,13 @@ */ class StdClassExporter extends ObjectExporter { - /** - * {@inheritDoc} - */ + #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { return $reflectionObject->getName() === \stdClass::class; } - /** - * {@inheritDoc} - */ + #[Override] public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array { $exported = $this->exporter->exportArray((array) $object, $path, $parentIds); From 4d443389b0aa3f4bf00d38197c47e2258249b023 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 12:24:57 +0100 Subject: [PATCH 101/129] Final classes --- src/Internal/ObjectExporter/AnyObjectExporter.php | 2 +- src/Internal/ObjectExporter/ClosureExporter.php | 2 +- src/Internal/ObjectExporter/EnumExporter.php | 2 +- src/Internal/ObjectExporter/InternalClassExporter.php | 2 +- src/Internal/ObjectExporter/SerializeExporter.php | 2 +- src/Internal/ObjectExporter/SetStateExporter.php | 2 +- src/Internal/ObjectExporter/StdClassExporter.php | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index ec648fb..a2dd946 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -15,7 +15,7 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class AnyObjectExporter extends ObjectExporter +final class AnyObjectExporter extends ObjectExporter { #[Override] public function supports(\ReflectionObject $reflectionObject) : bool diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 9c0074a..f2e6863 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -23,7 +23,7 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class ClosureExporter extends ObjectExporter +final class ClosureExporter extends ObjectExporter { private ?Parser $parser = null; diff --git a/src/Internal/ObjectExporter/EnumExporter.php b/src/Internal/ObjectExporter/EnumExporter.php index c833fae..b24f14f 100644 --- a/src/Internal/ObjectExporter/EnumExporter.php +++ b/src/Internal/ObjectExporter/EnumExporter.php @@ -13,7 +13,7 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class EnumExporter extends ObjectExporter +final class EnumExporter extends ObjectExporter { /** * See: https://github.com/vimeo/psalm/pull/8117 diff --git a/src/Internal/ObjectExporter/InternalClassExporter.php b/src/Internal/ObjectExporter/InternalClassExporter.php index bb00d7c..79ce7cf 100644 --- a/src/Internal/ObjectExporter/InternalClassExporter.php +++ b/src/Internal/ObjectExporter/InternalClassExporter.php @@ -13,7 +13,7 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class InternalClassExporter extends ObjectExporter +final class InternalClassExporter extends ObjectExporter { #[Override] public function supports(\ReflectionObject $reflectionObject) : bool diff --git a/src/Internal/ObjectExporter/SerializeExporter.php b/src/Internal/ObjectExporter/SerializeExporter.php index a03954d..6953ae8 100644 --- a/src/Internal/ObjectExporter/SerializeExporter.php +++ b/src/Internal/ObjectExporter/SerializeExporter.php @@ -12,7 +12,7 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class SerializeExporter extends ObjectExporter +final class SerializeExporter extends ObjectExporter { #[Override] public function supports(\ReflectionObject $reflectionObject) : bool diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index 67f9c9f..50de173 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -13,7 +13,7 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class SetStateExporter extends ObjectExporter +final class SetStateExporter extends ObjectExporter { #[Override] public function supports(\ReflectionObject $reflectionObject) : bool diff --git a/src/Internal/ObjectExporter/StdClassExporter.php b/src/Internal/ObjectExporter/StdClassExporter.php index 0c8d4b3..5c7e869 100644 --- a/src/Internal/ObjectExporter/StdClassExporter.php +++ b/src/Internal/ObjectExporter/StdClassExporter.php @@ -12,7 +12,7 @@ * * @internal This class is for internal use, and not part of the public API. It may change at any time without warning. */ -class StdClassExporter extends ObjectExporter +final class StdClassExporter extends ObjectExporter { #[Override] public function supports(\ReflectionObject $reflectionObject) : bool From 947090defc023b36bd2c4f3e695920524a80f12c Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 12:26:35 +0100 Subject: [PATCH 102/129] Clean up conditional code for PHP < 8.1 --- src/Internal/ObjectExporter/EnumExporter.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Internal/ObjectExporter/EnumExporter.php b/src/Internal/ObjectExporter/EnumExporter.php index b24f14f..9772d02 100644 --- a/src/Internal/ObjectExporter/EnumExporter.php +++ b/src/Internal/ObjectExporter/EnumExporter.php @@ -15,14 +15,10 @@ */ final class EnumExporter extends ObjectExporter { - /** - * See: https://github.com/vimeo/psalm/pull/8117 - * @psalm-suppress RedundantCondition - */ #[Override] public function supports(\ReflectionObject $reflectionObject) : bool { - return method_exists($reflectionObject, 'isEnum') && $reflectionObject->isEnum(); + return $reflectionObject->isEnum(); } #[Override] From fd573397d65cae1d1c287d2e2a68ed0b75428db6 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 12:46:02 +0100 Subject: [PATCH 103/129] Use str_ends_with() --- src/Internal/ObjectExporter/ClosureExporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index f2e6863..4de40fc 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -83,7 +83,7 @@ private function getParser() */ private function parseFile(string $filename, array $path) : array { - if (substr($filename, -16) === " : eval()'d code") { + if (str_ends_with($filename, " : eval()'d code")) { throw new ExportException("Closure defined in eval()'d code cannot be exported.", $path); } From d3246781c8f3bf130d7ac3a903fc435244e3da49 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 12:46:40 +0100 Subject: [PATCH 104/129] Use $object::class --- src/Internal/GenericExporter.php | 2 +- src/Internal/ObjectExporter/EnumExporter.php | 2 +- src/Internal/ObjectExporter/SetStateExporter.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 6787b3c..76d2d4b 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -261,7 +261,7 @@ public function exportObject(object $object, array $path, array $parentIds) : ar throw new ExportException(sprintf( 'Object of class "%s" has a circular reference at %s. ' . 'Circular references are currently not supported.', - get_class($object), + $object::class, ExportException::pathToString($this->visitedObjects[$parentId][$id]) ), $path); } diff --git a/src/Internal/ObjectExporter/EnumExporter.php b/src/Internal/ObjectExporter/EnumExporter.php index 9772d02..86fc1cb 100644 --- a/src/Internal/ObjectExporter/EnumExporter.php +++ b/src/Internal/ObjectExporter/EnumExporter.php @@ -27,7 +27,7 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra assert($object instanceof UnitEnum); return [ - get_class($object) . '::' . $object->name + $object::class . '::' . $object->name ]; } } diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index 50de173..b899d7b 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -74,7 +74,7 @@ private function getObjectVars(object $object, array $path) : array } if (array_key_exists($name, $result)) { - $className = get_class($object); + $className = $object::class; throw new ExportException( 'Class "' . $className . '" has overridden private property "' . $name . '". ' . From 034135fd832b415c3b8f235e95fce72b3c30052a Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 12:48:44 +0100 Subject: [PATCH 105/129] Add types --- src/Internal/GenericExporter.php | 32 ++++++------------- src/Internal/ObjectExporter.php | 5 +-- .../ObjectExporter/ClosureExporter.php | 5 +-- src/VarExporter.php | 2 +- tests/AbstractTestCase.php | 4 +-- tests/VarExporterTest.php | 4 +-- 6 files changed, 15 insertions(+), 37 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 76d2d4b..af7f27e 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -21,7 +21,7 @@ final class GenericExporter /** * @var ObjectExporter[] */ - private $objectExporters = []; + private array $objectExporters = []; /** * The visited objects, to detect circular references. @@ -34,52 +34,38 @@ final class GenericExporter /** * @psalm-readonly - * - * @var bool */ - public $addTypeHints; + public bool $addTypeHints; /** * @psalm-readonly - * - * @var bool */ - public $skipDynamicProperties; + public bool $skipDynamicProperties; /** * @psalm-readonly - * - * @var bool */ - public $inlineArray; + public bool $inlineArray; /** * @psalm-readonly - * - * @var bool */ - public $inlineScalarList; + public bool $inlineScalarList; /** * @psalm-readonly - * - * @var bool */ - public $closureSnapshotUses; + public bool $closureSnapshotUses; /** * @psalm-readonly - * - * @var bool */ - public $trailingCommaInArray; + public bool $trailingCommaInArray; /** * @psalm-readonly - * - * @var int */ - public $indentLevel; + public int $indentLevel; public function __construct(int $options, int $indentLevel = 0) { @@ -126,7 +112,7 @@ public function __construct(int $options, int $indentLevel = 0) * * @throws ExportException */ - public function export($var, array $path, array $parentIds) : array + public function export(mixed $var, array $path, array $parentIds) : array { switch ($type = gettype($var)) { case 'boolean': diff --git a/src/Internal/ObjectExporter.php b/src/Internal/ObjectExporter.php index 9b34614..66660f0 100644 --- a/src/Internal/ObjectExporter.php +++ b/src/Internal/ObjectExporter.php @@ -13,10 +13,7 @@ */ abstract class ObjectExporter { - /** - * @var GenericExporter - */ - protected $exporter; + protected GenericExporter $exporter; /** * @param GenericExporter $exporter diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 4de40fc..f91865f 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -59,10 +59,7 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra return [$code]; } - /** - * @return Parser - */ - private function getParser() + private function getParser(): Parser { if ($this->parser === null) { $this->parser = (new ParserFactory)->createForHostVersion(); diff --git a/src/VarExporter.php b/src/VarExporter.php index 138c431..7b3664e 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -84,7 +84,7 @@ final class VarExporter * * @throws ExportException */ - public static function export($var, int $options = 0, int $indentLevel = 0) : string + public static function export(mixed $var, int $options = 0, int $indentLevel = 0) : string { $exporter = new GenericExporter($options, $indentLevel); $lines = $exporter->export($var, [], []); diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 82e77a1..52e57d9 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -19,7 +19,7 @@ abstract class AbstractTestCase extends TestCase * * @return void */ - public function assertExportEquals(string $expected, $var, int $options = 0) : void + public function assertExportEquals(string $expected, mixed $var, int $options = 0) : void { // test the string output of export() @@ -61,7 +61,7 @@ public function assertExportEquals(string $expected, $var, int $options = 0) : v * * @return void */ - public function assertExportThrows(string $expectedMessage, $var, int $options = 0) : void + public function assertExportThrows(string $expectedMessage, mixed $var, int $options = 0) : void { $expectedMessageRegExp = '/' . implode('.*', array_map(fn(string $str) => preg_quote($str, '/'), explode('*', $expectedMessage))) . '/'; diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index bf67826..41934e9 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -356,10 +356,8 @@ public function testExportObjectWithCircularReference(): void /** * @dataProvider providerExportIndented - * - * @param mixed $var */ - public function testExportIndented($var, string $expected, int $options): void + public function testExportIndented(mixed $var, string $expected, int $options): void { $template = <<<'TPL' public function foo() From 5ffb4fb666abe2cefd0bdf829f8ed416a00e71c1 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 13:59:38 +0100 Subject: [PATCH 106/129] Replace gettype() with is_*() --- src/Internal/GenericExporter.php | 40 ++++++++++++++------------------ 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index af7f27e..b6a16fc 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -114,29 +114,25 @@ public function __construct(int $options, int $indentLevel = 0) */ public function export(mixed $var, array $path, array $parentIds) : array { - switch ($type = gettype($var)) { - case 'boolean': - case 'integer': - case 'double': - case 'string': - return [var_export($var, true)]; - - case 'NULL': - // lowercase null - return ['null']; - - case 'array': - /** @var array $var */ - return $this->exportArray($var, $path, $parentIds); - - case 'object': - /** @var object $var */ - return $this->exportObject($var, $path, $parentIds); - - default: - // resources - throw new ExportException(sprintf('Type "%s" is not supported.', $type), $path); + if ($var === null) { + return ['null']; } + + // bool, int, float, string + if (is_scalar($var)) { + return [var_export($var, true)]; + } + + if (is_array($var)) { + return $this->exportArray($var, $path, $parentIds); + } + + if (is_object($var)) { + return $this->exportObject($var, $path, $parentIds); + } + + // resources + throw new ExportException(sprintf('Type "%s" is not supported.', gettype($var)), $path); } /** From 004e7a32a8e9e4b9d16ba9a6e1c606c4eecdc56e Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 14:11:13 +0100 Subject: [PATCH 107/129] Clean up redundant documented types --- src/ExportException.php | 6 +----- src/Internal/GenericExporter.php | 3 --- src/Internal/ObjectExporter.php | 7 ------- src/Internal/ObjectExporter/AnyObjectExporter.php | 8 -------- src/Internal/ObjectExporter/ClosureExporter.php | 4 ---- .../ObjectExporter/ClosureExporter/PrettyPrinter.php | 5 ----- src/Internal/ObjectExporter/SetStateExporter.php | 6 ------ src/VarExporter.php | 2 -- tests/AbstractTestCase.php | 4 ---- 9 files changed, 1 insertion(+), 44 deletions(-) diff --git a/src/ExportException.php b/src/ExportException.php index 888cf9a..662c32c 100644 --- a/src/ExportException.php +++ b/src/ExportException.php @@ -9,9 +9,7 @@ final class ExportException extends \Exception { /** - * @param string $message - * @param string[] $path - * @param Throwable|null $previous + * @param string[] $path */ public function __construct(string $message, array $path, ?Throwable $previous = null) { @@ -26,8 +24,6 @@ public function __construct(string $message, array $path, ?Throwable $previous = * Returns a string representation of the given path. * * @param string[] $path - * - * @return string */ public static function pathToString(array $path) : string { diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index b6a16fc..e48400d 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -210,9 +210,6 @@ public function exportArray(array $array, array $path, array $parentIds) : array * Types considered scalar here are int, bool, float, string and null. * If the array is empty, this method returns true. * - * @param array $array - * - * @return bool */ private function isScalarList(array $array) : bool { diff --git a/src/Internal/ObjectExporter.php b/src/Internal/ObjectExporter.php index 66660f0..d3e7eee 100644 --- a/src/Internal/ObjectExporter.php +++ b/src/Internal/ObjectExporter.php @@ -15,9 +15,6 @@ abstract class ObjectExporter { protected GenericExporter $exporter; - /** - * @param GenericExporter $exporter - */ public function __construct(GenericExporter $exporter) { $this->exporter = $exporter; @@ -27,8 +24,6 @@ public function __construct(GenericExporter $exporter) * Returns whether this exporter supports the given object. * * @param \ReflectionObject $reflectionObject A reflection of the object. - * - * @return bool */ abstract public function supports(\ReflectionObject $reflectionObject) : bool; @@ -51,8 +46,6 @@ abstract public function export(object $object, \ReflectionObject $reflectionObj * * If the class has a constructor, reflection will be used to bypass it. * - * @param \ReflectionClass $class - * * @return string[] The lines of code. */ final protected function getCreateObjectCode(\ReflectionClass $class) : array diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index a2dd946..573d6c3 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -154,9 +154,6 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra /** * Returns the key of the given property in the object-to-array cast. * - * @param \ReflectionProperty $property - * - * @return string */ private function getPropertyKey(\ReflectionProperty $property) : string { @@ -173,11 +170,6 @@ private function getPropertyKey(\ReflectionProperty $property) : string return $name; } - /** - * @param string $var - * - * @return string - */ private function escapePropName(string $var) : string { if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $var) === 1) { diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index f91865f..1e6120e 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -131,8 +131,6 @@ private function resolveNames(array $ast) : array * @param int $line The line number where the closure is located in the source file. * @param string[] $path The path to the closure in the array/object graph. * - * @return Node\Expr\Closure - * * @throws ExportException */ private function getClosure( @@ -182,8 +180,6 @@ private function getClosure( * * @param ReflectionFunction $reflectionFunction Reflection of the closure. * @param Node\Expr\ArrowFunction $arrowFunction Parsed arrow function. - * - * @return Node\Expr\Closure */ private function convertArrowFunction( ReflectionFunction $reflectionFunction, diff --git a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php index 67773b2..97fd442 100644 --- a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php +++ b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php @@ -16,11 +16,6 @@ final class PrettyPrinter extends Standard { private int $varExporterNestingLevel = 0; - /** - * @param int $level - * - * @return void - */ public function setVarExporterNestingLevel(int $level) : void { $this->varExporterNestingLevel = $level; diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index b899d7b..01001e0 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -93,12 +93,6 @@ private function getObjectVars(object $object, array $path) : array return $result; } - /** - * @param object $object - * @param string $name - * - * @return bool - */ private function isDynamicProperty(object $object, string $name) : bool { $reflectionClass = new \ReflectionClass($object); diff --git a/src/VarExporter.php b/src/VarExporter.php index 7b3664e..de6bd7d 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -80,8 +80,6 @@ final class VarExporter * Combine multiple options with a bitwise OR `|` operator. * @param int $indentLevel The base output indentation level. * - * @return string - * * @throws ExportException */ public static function export(mixed $var, int $options = 0, int $indentLevel = 0) : string diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 52e57d9..8538a97 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -16,8 +16,6 @@ abstract class AbstractTestCase extends TestCase * @param string $expected The expected export() output. * @param mixed $var The variable to export. * @param int $options The options to pass to export(). - * - * @return void */ public function assertExportEquals(string $expected, mixed $var, int $options = 0) : void { @@ -58,8 +56,6 @@ public function assertExportEquals(string $expected, mixed $var, int $options = * @param string $expectedMessage The expected exception message. Can use '*' as a placeholder. * @param mixed $var The variable to export. * @param int $options The options to pass to export(). - * - * @return void */ public function assertExportThrows(string $expectedMessage, mixed $var, int $options = 0) : void { From 47c8aa4d2b096a8935101f9119e7dc6630857f6a Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 14:21:49 +0100 Subject: [PATCH 108/129] Clean up conditional code for PHP < 8.1 --- tests/ExportObjectTest.php | 99 ++++++++----------------- tests/VarExporterTest.php | 146 ++++++++++++------------------------- 2 files changed, 76 insertions(+), 169 deletions(-) diff --git a/tests/ExportObjectTest.php b/tests/ExportObjectTest.php index cabda41..f7a1922 100644 --- a/tests/ExportObjectTest.php +++ b/tests/ExportObjectTest.php @@ -174,23 +174,13 @@ public function testExportClassWithSetState(): void $object->foo = 'Hello'; $object->bar = 'World'; - if (PHP_VERSION_ID >=80100) { - $expected = <<<'PHP' -\Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'foo' => 'Hello', - 'bar' => 'World', - 'baz' => 'defaultValue' -]) -PHP; - } else { - $expected = <<<'PHP' -\Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'baz' => 'defaultValue', - 'foo' => 'Hello', - 'bar' => 'World' -]) -PHP; - } + $expected = <<<'PHP' + \Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => 'Hello', + 'bar' => 'World', + 'baz' => 'defaultValue' + ]) + PHP; $this->assertExportEquals($expected, $object); } @@ -202,21 +192,12 @@ public function testExportClassWithSetStateAndUnsetProperties(): void unset($object->bar); - if (PHP_VERSION_ID >= 80100) { - $expected = <<<'PHP' -\Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'foo' => null, - 'baz' => 'defaultValue' -]) -PHP; - } else { - $expected = <<<'PHP' -\Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'baz' => 'defaultValue', - 'foo' => null -]) -PHP; - } + $expected = <<<'PHP' + \Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => null, + 'baz' => 'defaultValue' + ]) + PHP; $this->assertExportEquals($expected, $object); } @@ -229,27 +210,15 @@ public function testExportClassWithSetStateAndDynamicProperties(): void $object->dynamic = 'Dynamic property'; $object->{'123'} = 'Numeric dynamic property'; - if (PHP_VERSION_ID >= 80100) { - $expected = <<<'PHP' -\Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'foo' => 'Hello', - 'bar' => 'World', - 'baz' => 'defaultValue', - 'dynamic' => 'Dynamic property', - 123 => 'Numeric dynamic property' -]) -PHP; - } else { - $expected = <<<'PHP' -\Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'baz' => 'defaultValue', - 'foo' => 'Hello', - 'bar' => 'World', - 'dynamic' => 'Dynamic property', - 123 => 'Numeric dynamic property' -]) -PHP; - } + $expected = <<<'PHP' + \Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => 'Hello', + 'bar' => 'World', + 'baz' => 'defaultValue', + 'dynamic' => 'Dynamic property', + 123 => 'Numeric dynamic property' + ]) + PHP; $this->assertExportEquals($expected, $object); } @@ -261,23 +230,13 @@ public function testExportClassWithSetStateAndDynamicProperties_SkipDynamicPrope $object->bar = 'World'; $object->dynamic = 'Dynamic property'; - if (PHP_VERSION_ID >= 80100) { - $expected = <<<'PHP' -\Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'foo' => 'Hello', - 'bar' => 'World', - 'baz' => 'defaultValue' -]) -PHP; - } else { - $expected = <<<'PHP' -\Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'baz' => 'defaultValue', - 'foo' => 'Hello', - 'bar' => 'World' -]) -PHP; - } + $expected = <<<'PHP' + \Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => 'Hello', + 'bar' => 'World', + 'baz' => 'defaultValue' + ]) + PHP; $this->assertExportEquals($expected, $object, VarExporter::SKIP_DYNAMIC_PROPERTIES); } diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 41934e9..70dc030 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -49,107 +49,55 @@ public function testMixedVar(): void 'aCustomObject' => $myObject ]; - if (PHP_VERSION_ID >= 80100) { $expected = <<<'PHP' -[ - 'aString' => 'Hello', - 'aTrue' => true, - 'aFalse' => false, - 'aNull' => null, - 'aFloat' => 0.75, - 'anInt' => 123, - 'aNumericArray' => [ - 'a', - 'b', - null, - [ - 'c' => 'd' - ] - ], - 'anAssociativeArray' => [ - 'a' => 'b', - 'c' => [ - 'd' => 'e', - 'f' => [ - 'g' => [ - [] - ] - ] - ] - ], - 'anObject' => (object) [ - 'type' => 'string', - '$ref' => '#/components/schema/User', - 'items' => (object) [ - 'foo' => 'bar', - 'empty' => (object) [] - ] - ], - 'aCustomObject' => (static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; - - $object->foo = 'hello'; - $object->bar = \Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'foo' => 'SetState.foo', - 'bar' => 'SetState.bar', - 'baz' => 'defaultValue' - ]); - - return $object; - })() -] -PHP; - } else { - $expected = <<<'PHP' -[ - 'aString' => 'Hello', - 'aTrue' => true, - 'aFalse' => false, - 'aNull' => null, - 'aFloat' => 0.75, - 'anInt' => 123, - 'aNumericArray' => [ - 'a', - 'b', - null, - [ - 'c' => 'd' - ] - ], - 'anAssociativeArray' => [ - 'a' => 'b', - 'c' => [ - 'd' => 'e', - 'f' => [ - 'g' => [ - [] + [ + 'aString' => 'Hello', + 'aTrue' => true, + 'aFalse' => false, + 'aNull' => null, + 'aFloat' => 0.75, + 'anInt' => 123, + 'aNumericArray' => [ + 'a', + 'b', + null, + [ + 'c' => 'd' + ] + ], + 'anAssociativeArray' => [ + 'a' => 'b', + 'c' => [ + 'd' => 'e', + 'f' => [ + 'g' => [ + [] + ] + ] + ] + ], + 'anObject' => (object) [ + 'type' => 'string', + '$ref' => '#/components/schema/User', + 'items' => (object) [ + 'foo' => 'bar', + 'empty' => (object) [] + ] + ], + 'aCustomObject' => (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; + + $object->foo = 'hello'; + $object->bar = \Brick\VarExporter\Tests\Classes\SetState::__set_state([ + 'foo' => 'SetState.foo', + 'bar' => 'SetState.bar', + 'baz' => 'defaultValue' + ]); + + return $object; + })() ] - ] - ] - ], - 'anObject' => (object) [ - 'type' => 'string', - '$ref' => '#/components/schema/User', - 'items' => (object) [ - 'foo' => 'bar', - 'empty' => (object) [] - ] - ], - 'aCustomObject' => (static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; - - $object->foo = 'hello'; - $object->bar = \Brick\VarExporter\Tests\Classes\SetState::__set_state([ - 'baz' => 'defaultValue', - 'foo' => 'SetState.foo', - 'bar' => 'SetState.bar' - ]); - - return $object; - })() -] -PHP; - } + PHP; $this->assertExportEquals($expected, $var); } From f8e44d3f4bf091b1c40fd95f85696b7a9f7dd996 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 14:45:47 +0100 Subject: [PATCH 109/129] Use flexible nowdocs --- tests/ExportClosureTest.php | 179 ++++++++------- tests/ExportObjectTest.php | 424 ++++++++++++++++++------------------ tests/VarExporterTest.php | 234 ++++++++++---------- 3 files changed, 418 insertions(+), 419 deletions(-) diff --git a/tests/ExportClosureTest.php b/tests/ExportClosureTest.php index 2bba287..67e4e05 100644 --- a/tests/ExportClosureTest.php +++ b/tests/ExportClosureTest.php @@ -28,10 +28,10 @@ public function testExportSimpleClosure(): void }; $expected = <<<'PHP' -function () { - return 'Hello, world!'; -} -PHP; + function () { + return 'Hello, world!'; + } + PHP; $this->assertExportEquals($expected, $var); } @@ -59,20 +59,20 @@ public function testExportNestedComplexClosure(): void ]; $expected = <<<'PHP' -[ - (object) [ - 'callback' => function () { - return function (\Brick\VarExporter\Tests\Classes\PublicPropertiesOnly $a, int $b, string &$c, string ...$d): ?string { - $a->foo += $b; - $c = $a->bar; - $a->bar = implode('', $d); - $this->someProp = [$a->foo, $a->bar, $c]; - return $this->someProp['c']; - }; - } - ] -] -PHP; + [ + (object) [ + 'callback' => function () { + return function (\Brick\VarExporter\Tests\Classes\PublicPropertiesOnly $a, int $b, string &$c, string ...$d): ?string { + $a->foo += $b; + $c = $a->bar; + $a->bar = implode('', $d); + $this->someProp = [$a->foo, $a->bar, $c]; + return $this->someProp['c']; + }; + } + ] + ] + PHP; $this->assertExportEquals($expected, $var); } @@ -92,15 +92,15 @@ public function testExportNamespacedCode(): void }; $expected = <<<'PHP' -function (\Brick\VarExporter\Tests\Classes\SetState $a): array { - return ['callback' => function (\Brick\VarExporter\Tests\Classes\SetState $a): \Brick\VarExporter\Tests\Classes\NoProperties { - strlen(PHP_VERSION); - \Brick\VarExporter\Dummy\Functions\imported_function(\Brick\VarExporter\Dummy\Constants\IMPORTED_CONSTANT); - \Brick\VarExporter\Dummy\Functions\explicitly_namespaced_function(\Brick\VarExporter\Dummy\Constants\EXPLICITLY_NAMESPACED_CONSTANT); - return new \Brick\VarExporter\Tests\Classes\NoProperties(); - }]; -} -PHP; + function (\Brick\VarExporter\Tests\Classes\SetState $a): array { + return ['callback' => function (\Brick\VarExporter\Tests\Classes\SetState $a): \Brick\VarExporter\Tests\Classes\NoProperties { + strlen(PHP_VERSION); + \Brick\VarExporter\Dummy\Functions\imported_function(\Brick\VarExporter\Dummy\Constants\IMPORTED_CONSTANT); + \Brick\VarExporter\Dummy\Functions\explicitly_namespaced_function(\Brick\VarExporter\Dummy\Constants\EXPLICITLY_NAMESPACED_CONSTANT); + return new \Brick\VarExporter\Tests\Classes\NoProperties(); + }]; + } + PHP; $this->assertExportEquals($expected, $var); } @@ -112,34 +112,34 @@ public function testExportClosureWithStringsContainingLikeBreaks(): void World!'; $b = <<assertExportEquals($expected, $var, VarExporter::ADD_RETURN); } @@ -168,12 +168,12 @@ public function testExportClosureWithUseAsVars(): void }; $expected = <<<'PHP' -return function () { - $foo = 'bar'; - return $foo; -}; + return function () { + $foo = 'bar'; + return $foo; + }; -PHP; + PHP; $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); } @@ -191,32 +191,31 @@ public function testExportClosureWithUseClosure(): void }; $expected = <<<'PHP' -return function () { - $sub = function () { - $foo = 'bar'; - return $foo; - }; - return $sub(); -}; + return function () { + $sub = function () { + $foo = 'bar'; + return $foo; + }; + return $sub(); + }; -PHP; + PHP; $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); } - public function testExportArrowFunction(): void { $var = [fn ($planet) => 'hello ' . $planet]; // Wrapping in array for valid syntax PHP <7.4 $expected = <<<'PHP' -return [ - function ($planet) { - return 'hello ' . $planet; - } -]; + return [ + function ($planet) { + return 'hello ' . $planet; + } + ]; -PHP; + PHP; $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN); } @@ -241,14 +240,14 @@ public function testExportArrowFunctionWithContextVarAsVar(): void $var = [fn ($planet) => $greet . ' ' . $planet]; $expected = <<<'PHP' -return [ - function ($planet) { - $greet = 'hello'; - return $greet . ' ' . $planet; - } -]; + return [ + function ($planet) { + $greet = 'hello'; + return $greet . ' ' . $planet; + } + ]; -PHP; + PHP; $this->assertExportEquals($expected, $var, VarExporter::ADD_RETURN | VarExporter::CLOSURE_SNAPSHOT_USES); } @@ -264,16 +263,16 @@ public function testExportEnumMatchFunction(): void ]; $expected = <<<'PHP' -[ - (object) [ - 'callback' => function (\Brick\VarExporter\Tests\Classes\Enum $enum): string { - return match ($enum) { - \Brick\VarExporter\Tests\Classes\Enum::TEST => 'foo', - }; - } - ] -] -PHP; + [ + (object) [ + 'callback' => function (\Brick\VarExporter\Tests\Classes\Enum $enum): string { + return match ($enum) { + \Brick\VarExporter\Tests\Classes\Enum::TEST => 'foo', + }; + } + ] + ] + PHP; $this->assertExportEquals($expected, $var); } @@ -281,11 +280,11 @@ public function testExportEnumMatchFunction(): void public function testExportClosureDefinedInEval(): void { $var = eval(<<assertExportThrows("Closure defined in eval()'d code cannot be exported.", $var); } diff --git a/tests/ExportObjectTest.php b/tests/ExportObjectTest.php index f7a1922..e502416 100644 --- a/tests/ExportObjectTest.php +++ b/tests/ExportObjectTest.php @@ -33,14 +33,14 @@ public function testExportStdClass(): void $object->baz->foo = 'Hello'; $expected = <<<'PHP' -(object) [ - 'foo' => 'Hello', - 'bar' => 'bar', - 'baz' => (object) [ - 'foo' => 'Hello' - ] -] -PHP; + (object) [ + 'foo' => 'Hello', + 'bar' => 'bar', + 'baz' => (object) [ + 'foo' => 'Hello' + ] + ] + PHP; $this->assertExportEquals($expected, $object); } @@ -63,17 +63,17 @@ public function testExportClassWithDynamicPropertiesOnly(): void $object->{'123'} = 'Numeric dynamic prop'; $expected = <<<'PHP' -(static function() { - $object = new \Brick\VarExporter\Tests\Classes\NoProperties; + (static function() { + $object = new \Brick\VarExporter\Tests\Classes\NoProperties; - $object->x = 1.0; - $object->dynamicProp = 'Hello'; - $object->{'$weird%Prop'} = 'World'; - $object->{'123'} = 'Numeric dynamic prop'; + $object->x = 1.0; + $object->dynamicProp = 'Hello'; + $object->{'$weird%Prop'} = 'World'; + $object->{'123'} = 'Numeric dynamic prop'; - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -94,13 +94,13 @@ public function testExportClassWithConstructorAndNoProperties(): void $object = new ConstructorAndNoProperties(); $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\ConstructorAndNoProperties::class); - $object = $class->newInstanceWithoutConstructor(); + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\ConstructorAndNoProperties::class); + $object = $class->newInstanceWithoutConstructor(); - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object, VarExporter::SKIP_DYNAMIC_PROPERTIES); } @@ -112,15 +112,15 @@ public function testExportClassWithPublicPropertiesOnly(): void $object->bar = 'World'; $expected = <<<'PHP' -(static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; + (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; - $object->foo = 'Hello'; - $object->bar = 'World'; + $object->foo = 'Hello'; + $object->bar = 'World'; - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -133,16 +133,16 @@ public function testExportObjectWithPublicAndDynamicProperties(): void $object->dynamic = 'Dynamic'; $expected = <<<'PHP' -(static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; + (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; - $object->foo = 'Hello'; - $object->bar = 'World'; - $object->dynamic = 'Dynamic'; + $object->foo = 'Hello'; + $object->bar = 'World'; + $object->dynamic = 'Dynamic'; - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -155,15 +155,15 @@ public function testExportObjectWithPublicAndDynamicProperties_SkipDynamicProper $object->dynamic = 'Dynamic'; $expected = <<<'PHP' -(static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; + (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; - $object->foo = 'Hello'; - $object->bar = 'World'; + $object->foo = 'Hello'; + $object->bar = 'World'; - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object, VarExporter::SKIP_DYNAMIC_PROPERTIES); } @@ -257,16 +257,16 @@ public function testExportClassWithPrivateConstructor(): void $object->bar = 'Bar'; $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\PrivateConstructor::class); - $object = $class->newInstanceWithoutConstructor(); + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\PrivateConstructor::class); + $object = $class->newInstanceWithoutConstructor(); - $object->foo = 'Foo'; - $object->bar = 'Bar'; + $object->foo = 'Foo'; + $object->bar = 'Bar'; - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -276,16 +276,16 @@ public function testExportClassWithPublicPropertiesAndConstructor(): void $object = new PublicPropertiesWithConstructor(); $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\PublicPropertiesWithConstructor::class); - $object = $class->newInstanceWithoutConstructor(); + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\PublicPropertiesWithConstructor::class); + $object = $class->newInstanceWithoutConstructor(); - $object->foo = 'DefaultFoo'; - $object->bar = 0; + $object->foo = 'DefaultFoo'; + $object->bar = 0; - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -298,20 +298,20 @@ public function testExportClassWithReadonlyPublicPropertiesAndConstructor(): voi $object = new ReadonlyPropertiesWithConstructor('public readonly', 'private readonly', 'public'); $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor::class); - $object = $class->newInstanceWithoutConstructor(); + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor::class); + $object = $class->newInstanceWithoutConstructor(); - $object->baz = 'public'; + $object->baz = 'public'; - (function() { - $this->foo = 'public readonly'; - $this->bar = 'private readonly'; - })->bindTo($object, \Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor::class)(); + (function() { + $this->foo = 'public readonly'; + $this->bar = 'private readonly'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor::class)(); - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -328,19 +328,19 @@ public function testExportClassWithStateAndReadonlyPublicProperties(): void })->bindTo($object, PublicReadonlyPropertiesWithoutConstructor::class)(); $expected = <<<'PHP' -(static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor; + (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor; - unset($object->baz); + unset($object->baz); - (function() { - $this->foo = 'foo'; - unset($this->bar); - })->bindTo($object, \Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor::class)(); + (function() { + $this->foo = 'foo'; + unset($this->bar); + })->bindTo($object, \Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor::class)(); - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -353,20 +353,20 @@ public function testExportClassWithSerializeMagicMethods(): void $object->bar = [1, 2]; $expected = <<<'PHP' -(static function() { - $object = new \Brick\VarExporter\Tests\Classes\SerializeMagicMethods; - - $object->__unserialize([ - 'foo' => 'Foo', - 'bar' => [ - 1, - 2 - ] - ]); - - return $object; -})() -PHP; + (static function() { + $object = new \Brick\VarExporter\Tests\Classes\SerializeMagicMethods; + + $object->__unserialize([ + 'foo' => 'Foo', + 'bar' => [ + 1, + 2 + ] + ]); + + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -376,18 +376,18 @@ public function testExportClassWithSerializeMagicMethodsAndConstructor(): void $object = new SerializeMagicMethodsWithConstructor('Test', 1234); $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\SerializeMagicMethodsWithConstructor::class); - $object = $class->newInstanceWithoutConstructor(); + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\SerializeMagicMethodsWithConstructor::class); + $object = $class->newInstanceWithoutConstructor(); - $object->__unserialize([ - 'foo' => 'Test', - 'bar' => 1234 - ]); + $object->__unserialize([ + 'foo' => 'Test', + 'bar' => 1234 + ]); - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -397,20 +397,20 @@ public function testExportClassWithSerializeMagicMethodsAndConstructor_AddTypeHi $object = new SerializeMagicMethodsWithConstructor('Test', 1234); $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\SerializeMagicMethodsWithConstructor::class); + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\SerializeMagicMethodsWithConstructor::class); - /** @var \Brick\VarExporter\Tests\Classes\SerializeMagicMethodsWithConstructor $object */ - $object = $class->newInstanceWithoutConstructor(); + /** @var \Brick\VarExporter\Tests\Classes\SerializeMagicMethodsWithConstructor $object */ + $object = $class->newInstanceWithoutConstructor(); - $object->__unserialize([ - 'foo' => 'Test', - 'bar' => 1234 - ]); + $object->__unserialize([ + 'foo' => 'Test', + 'bar' => 1234 + ]); - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object, VarExporter::ADD_TYPE_HINTS); } @@ -421,36 +421,36 @@ public function testExportClassHierarchy(): void $object->dynamicProperty = 'A property declared dynamically'; $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\Hierarchy\C::class); - $object = $class->newInstanceWithoutConstructor(); - - $object->publicInC = 'public in C'; - $object->publicInB = 'public in B'; - $object->publicInA = 'public in A'; - $object->dynamicProperty = 'A property declared dynamically'; - - (function() { - $this->privateInC = 'private in C'; - $this->protectedInC = 'protected in C'; - $this->privateOverridden = 'in C'; - $this->protectedInB = 'protected in B'; - $this->protectedInA = 'protected in A'; - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\C::class)(); - - (function() { - $this->privateInB = 'private in B'; - $this->privateOverridden = 'in B'; - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\B::class)(); - - (function() { - $this->privateInA = 'private in A'; - $this->privateOverridden = 'in A'; - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\A::class)(); - - return $object; -})() -PHP; + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\Hierarchy\C::class); + $object = $class->newInstanceWithoutConstructor(); + + $object->publicInC = 'public in C'; + $object->publicInB = 'public in B'; + $object->publicInA = 'public in A'; + $object->dynamicProperty = 'A property declared dynamically'; + + (function() { + $this->privateInC = 'private in C'; + $this->protectedInC = 'protected in C'; + $this->privateOverridden = 'in C'; + $this->protectedInB = 'protected in B'; + $this->protectedInA = 'protected in A'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\C::class)(); + + (function() { + $this->privateInB = 'private in B'; + $this->privateOverridden = 'in B'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\B::class)(); + + (function() { + $this->privateInA = 'private in A'; + $this->privateOverridden = 'in A'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\A::class)(); + + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -461,40 +461,40 @@ public function testExportClassHierarchy_AddTypeHints_SkipDynamicProperties(): v $object->dynamicProperty = 'A property declared dynamically'; $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\Hierarchy\C::class); - - /** @var \Brick\VarExporter\Tests\Classes\Hierarchy\C $object */ - $object = $class->newInstanceWithoutConstructor(); - - $object->publicInC = 'public in C'; - $object->publicInB = 'public in B'; - $object->publicInA = 'public in A'; - - (function() { - /** @var \Brick\VarExporter\Tests\Classes\Hierarchy\C $this */ - $this->privateInC = 'private in C'; - $this->protectedInC = 'protected in C'; - $this->privateOverridden = 'in C'; - $this->protectedInB = 'protected in B'; - $this->protectedInA = 'protected in A'; - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\C::class)(); - - (function() { - /** @var \Brick\VarExporter\Tests\Classes\Hierarchy\B $this */ - $this->privateInB = 'private in B'; - $this->privateOverridden = 'in B'; - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\B::class)(); - - (function() { - /** @var \Brick\VarExporter\Tests\Classes\Hierarchy\A $this */ - $this->privateInA = 'private in A'; - $this->privateOverridden = 'in A'; - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\A::class)(); - - return $object; -})() -PHP; + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\Hierarchy\C::class); + + /** @var \Brick\VarExporter\Tests\Classes\Hierarchy\C $object */ + $object = $class->newInstanceWithoutConstructor(); + + $object->publicInC = 'public in C'; + $object->publicInB = 'public in B'; + $object->publicInA = 'public in A'; + + (function() { + /** @var \Brick\VarExporter\Tests\Classes\Hierarchy\C $this */ + $this->privateInC = 'private in C'; + $this->protectedInC = 'protected in C'; + $this->privateOverridden = 'in C'; + $this->protectedInB = 'protected in B'; + $this->protectedInA = 'protected in A'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\C::class)(); + + (function() { + /** @var \Brick\VarExporter\Tests\Classes\Hierarchy\B $this */ + $this->privateInB = 'private in B'; + $this->privateOverridden = 'in B'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\B::class)(); + + (function() { + /** @var \Brick\VarExporter\Tests\Classes\Hierarchy\A $this */ + $this->privateInA = 'private in A'; + $this->privateOverridden = 'in A'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\A::class)(); + + return $object; + })() + PHP; $this->assertExportEquals($expected, $object, VarExporter::ADD_TYPE_HINTS | VarExporter::SKIP_DYNAMIC_PROPERTIES); } @@ -518,35 +518,35 @@ public function testExportClassHierarchyWithUnsetProperties(): void })->bindTo($object, Hierarchy\A::class)(); $expected = <<<'PHP' -(static function() { - $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\Hierarchy\C::class); - $object = $class->newInstanceWithoutConstructor(); - - $object->publicInC = 'public in C'; - $object->publicInA = null; - unset($object->publicInB); - - (function() { - $this->protectedInC = 'protected in C'; - $this->privateOverridden = 'in C'; - $this->protectedInA = 'protected in A'; - unset($this->privateInC); - unset($this->protectedInB); - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\C::class)(); - - (function() { - $this->privateInB = 'private in B'; - $this->privateOverridden = 'in B'; - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\B::class)(); - - (function() { - $this->privateInA = 'private in A'; - unset($this->privateOverridden); - })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\A::class)(); - - return $object; -})() -PHP; + (static function() { + $class = new \ReflectionClass(\Brick\VarExporter\Tests\Classes\Hierarchy\C::class); + $object = $class->newInstanceWithoutConstructor(); + + $object->publicInC = 'public in C'; + $object->publicInA = null; + unset($object->publicInB); + + (function() { + $this->protectedInC = 'protected in C'; + $this->privateOverridden = 'in C'; + $this->protectedInA = 'protected in A'; + unset($this->privateInC); + unset($this->protectedInB); + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\C::class)(); + + (function() { + $this->privateInB = 'private in B'; + $this->privateOverridden = 'in B'; + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\B::class)(); + + (function() { + $this->privateInA = 'private in A'; + unset($this->privateOverridden); + })->bindTo($object, \Brick\VarExporter\Tests\Classes\Hierarchy\A::class)(); + + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -570,8 +570,8 @@ public function testExportEnum(): void $object = Enum::TEST; $expected = <<<'PHP' -Brick\VarExporter\Tests\Classes\Enum::TEST -PHP; + Brick\VarExporter\Tests\Classes\Enum::TEST + PHP; $this->assertExportEquals($expected, $object); } diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 70dc030..45dbe19 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -108,16 +108,16 @@ public function testExportObjectPropWithSpecialChars(): void $object->{'$ref'} = '#/components/schemas/User'; $expected = <<<'PHP' -(static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; + (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; - $object->foo = null; - $object->bar = null; - $object->{'$ref'} = '#/components/schemas/User'; + $object->foo = null; + $object->bar = null; + $object->{'$ref'} = '#/components/schemas/User'; - return $object; -})() -PHP; + return $object; + })() + PHP; $this->assertExportEquals($expected, $object); } @@ -138,8 +138,8 @@ public function testInlineArray(): void ]; $expected = <<<'PHP' -['one' => ['hello', 'world', 123, true, false, null, 7.5], 'two' => ['hello', 'world', [1 => 'one', 2 => 'two', 3 => 'three']]] -PHP; + ['one' => ['hello', 'world', 123, true, false, null, 7.5], 'two' => ['hello', 'world', [1 => 'one', 2 => 'two', 3 => 'three']]] + PHP; $this->assertExportEquals($expected, $var, VarExporter::INLINE_ARRAY); } @@ -152,15 +152,15 @@ public function testInlineScalarList(): void ]; $expected = <<<'PHP' -[ - 'one' => ['hello', 'world', 123, true, false, null, 7.5], - 'two' => [ - 'hello', - 'world', - ['one', 'two', 'three'] - ] -] -PHP; + [ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => [ + 'hello', + 'world', + ['one', 'two', 'three'] + ] + ] + PHP; $this->assertExportEquals($expected, $var, VarExporter::INLINE_SCALAR_LIST); } @@ -173,15 +173,15 @@ public function testTrailingCommaInArray(): void ]; $expected = <<<'PHP' -[ - 'one' => ['hello', 'world', 123, true, false, null, 7.5], - 'two' => [ - 'hello', - 'world', - ['one', 'two', 'three'], - ], -] -PHP; + [ + 'one' => ['hello', 'world', 123, true, false, null, 7.5], + 'two' => [ + 'hello', + 'world', + ['one', 'two', 'three'], + ], + ] + PHP; $this->assertExportEquals($expected, $var, VarExporter::INLINE_SCALAR_LIST | VarExporter::TRAILING_COMMA_IN_ARRAY); } @@ -194,12 +194,12 @@ public function testExportDateTime(): void $var = \DateTime::createFromFormat($format, '2020-03-09 18:51:23.000000', $timezone); $expected = <<<'PHP' -\DateTime::__set_state([ - 'date' => '2020-03-09 18:51:23.000000', - 'timezone_type' => 3, - 'timezone' => 'Europe/Berlin' -]) -PHP; + \DateTime::__set_state([ + 'date' => '2020-03-09 18:51:23.000000', + 'timezone_type' => 3, + 'timezone' => 'Europe/Berlin' + ]) + PHP; $this->assertExportEquals($expected, $var); } @@ -212,12 +212,12 @@ public function testExportDateTimeImmutable(): void $var = \DateTimeImmutable::createFromFormat($format, '2020-03-10 17:06:19.000000', $timezone); $expected = <<<'PHP' -\DateTimeImmutable::__set_state([ - 'date' => '2020-03-10 17:06:19.000000', - 'timezone_type' => 3, - 'timezone' => 'Europe/Berlin' -]) -PHP; + \DateTimeImmutable::__set_state([ + 'date' => '2020-03-10 17:06:19.000000', + 'timezone_type' => 3, + 'timezone' => 'Europe/Berlin' + ]) + PHP; $this->assertExportEquals($expected, $var); } @@ -261,25 +261,25 @@ public function testExportObjectTwiceWithoutCircularReference(): void ]; $expected = <<<'PHP' -[ - 'x' => (static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; + [ + 'x' => (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; - $object->foo = 'Foo'; - $object->bar = 'Bar'; + $object->foo = 'Foo'; + $object->bar = 'Bar'; - return $object; - })(), - 'y' => (static function() { - $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; + return $object; + })(), + 'y' => (static function() { + $object = new \Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; - $object->foo = 'Foo'; - $object->bar = 'Bar'; + $object->foo = 'Foo'; + $object->bar = 'Bar'; - return $object; - })() -] -PHP; + return $object; + })() + ] + PHP; $this->assertExportEquals($expected, $var); } @@ -308,11 +308,11 @@ public function testExportObjectWithCircularReference(): void public function testExportIndented(mixed $var, string $expected, int $options): void { $template = <<<'TPL' -public function foo() -{ - $data = {{exported}}; -} -TPL; + public function foo() + { + $data = {{exported}}; + } + TPL; $exported = VarExporter::export($var, $options, 1); $result = str_replace('{{exported}}', $exported, $template); @@ -325,27 +325,27 @@ public function providerExportIndented(): iterable // Array $var = ['one' => ['hello', true], 'two' => 2]; $expected = <<<'PHP' -public function foo() -{ - $data = [ - 'one' => [ - 'hello', - true - ], - 'two' => 2 - ]; -} -PHP; + public function foo() + { + $data = [ + 'one' => [ + 'hello', + true + ], + 'two' => 2 + ]; + } + PHP; yield [$var, $expected, 0]; // Null $var = null; $expected = <<<'PHP' -public function foo() -{ - $data = null; -} -PHP; + public function foo() + { + $data = null; + } + PHP; yield [$var, $expected, 0]; // Closure @@ -353,13 +353,13 @@ public function foo() return 'Hello, world!'; }; $expected = <<<'PHP' -public function foo() -{ - $data = function () { - return 'Hello, world!'; - }; -} -PHP; + public function foo() + { + $data = function () { + return 'Hello, world!'; + }; + } + PHP; yield [$var, $expected, 0]; $foo = 'bar'; @@ -371,17 +371,17 @@ public function foo() }; $expected = <<<'PHP' -public function foo() -{ - $data = function () { - $sub = function () { - $foo = 'bar'; - return $foo; - }; - return $sub(); - }; -} -PHP; + public function foo() + { + $data = function () { + $sub = function () { + $foo = 'bar'; + return $foo; + }; + return $sub(); + }; + } + PHP; yield [$var, $expected, VarExporter::CLOSURE_SNAPSHOT_USES]; $var = function () { @@ -389,36 +389,36 @@ public function foo() World!'; $b = << Date: Thu, 20 Feb 2025 14:50:36 +0100 Subject: [PATCH 110/129] Update PHPUnit to 10.5 --- .gitignore | 3 +-- composer.json | 2 +- phpunit.xml | 6 +++--- tests/VarExporterTest.php | 7 +++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 32df3db..8ae88e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /vendor /composer.lock -/.phpunit.result.cache - +/.phpunit.cache diff --git a/composer.json b/composer.json index e2f2cdd..5355475 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "nikic/php-parser": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^10.5", "php-coveralls/php-coveralls": "^2.2", "vimeo/psalm": "6.8.4" }, diff --git a/phpunit.xml b/phpunit.xml index d6cb2ff..91289ba 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,13 +1,13 @@ - + tests - + src - + diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 45dbe19..a4fb718 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -8,6 +8,7 @@ use Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; use Brick\VarExporter\Tests\Classes\SetState; use Brick\VarExporter\VarExporter; +use PHPUnit\Framework\Attributes\DataProvider; class VarExporterTest extends AbstractTestCase { @@ -302,9 +303,7 @@ public function testExportObjectWithCircularReference(): void ]); } - /** - * @dataProvider providerExportIndented - */ + #[DataProvider('providerExportIndented')] public function testExportIndented(mixed $var, string $expected, int $options): void { $template = <<<'TPL' @@ -320,7 +319,7 @@ public function foo() $this->assertEquals($expected, $result); } - public function providerExportIndented(): iterable + public static function providerExportIndented(): iterable { // Array $var = ['one' => ['hello', true], 'two' => 2]; From 774ce0e3d8ca0b3a1d6a03a618ae88584adaf0f6 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 14:57:35 +0100 Subject: [PATCH 111/129] Use int comparison for bitmasks --- src/Internal/GenericExporter.php | 10 +++++----- src/VarExporter.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index e48400d..9b9df97 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -71,25 +71,25 @@ public function __construct(int $options, int $indentLevel = 0) { $this->objectExporters[] = new ObjectExporter\StdClassExporter($this); - if (! ($options & VarExporter::NO_CLOSURES)) { + if (($options & VarExporter::NO_CLOSURES) === 0) { $this->objectExporters[] = new ObjectExporter\ClosureExporter($this); } - if (! ($options & VarExporter::NO_SET_STATE)) { + if (($options & VarExporter::NO_SET_STATE) === 0) { $this->objectExporters[] = new ObjectExporter\SetStateExporter($this); } $this->objectExporters[] = new ObjectExporter\InternalClassExporter($this); - if (! ($options & VarExporter::NO_SERIALIZE)) { + if (($options & VarExporter::NO_SERIALIZE) === 0) { $this->objectExporters[] = new ObjectExporter\SerializeExporter($this); } - if (! ($options & VarExporter::NO_ENUMS)) { + if (($options & VarExporter::NO_ENUMS) === 0) { $this->objectExporters[] = new ObjectExporter\EnumExporter($this); } - if (! ($options & VarExporter::NOT_ANY_OBJECT)) { + if (($options & VarExporter::NOT_ANY_OBJECT) === 0) { $this->objectExporters[] = new ObjectExporter\AnyObjectExporter($this); } diff --git a/src/VarExporter.php b/src/VarExporter.php index de6bd7d..7258f33 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -99,7 +99,7 @@ public static function export(mixed $var, int $options = 0, int $indentLevel = 0 $export = $firstLine . PHP_EOL . implode(PHP_EOL, $lines); } - if ($options & self::ADD_RETURN) { + if (($options & self::ADD_RETURN) !== 0) { return 'return ' . $export . ';' . PHP_EOL; } From 881a1fd708589caaf4a9fa38b31ae03715ccdf35 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 14:54:11 +0100 Subject: [PATCH 112/129] Apply some rector code style fixes --- src/Internal/GenericExporter.php | 6 +----- src/Internal/ObjectExporter/SetStateExporter.php | 3 +-- tests/AbstractTestCase.php | 6 ++---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 9b9df97..7757ccd 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -170,11 +170,7 @@ public function exportArray(array $array, array $path, array $parentIds) : array $exported = $this->export($value, $newPath, $parentIds); if ($inline) { - if ($isList) { - $result[] = $exported[0]; - } else { - $result[] = var_export($key, true) . ' => ' . $exported[0]; - } + $result[] = $isList ? $exported[0] : var_export($key, true) . ' => ' . $exported[0]; } else { $prepend = ''; $append = ''; diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index 01001e0..a33c79e 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -35,9 +35,8 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra $vars = $this->getObjectVars($object, $path); $exportedVars = $this->exporter->exportArray($vars, $path, $parentIds); - $exportedVars = $this->exporter->wrap($exportedVars, '\\' . $className . '::__set_state(', ')'); - return $exportedVars; + return $this->exporter->wrap($exportedVars, '\\' . $className . '::__set_state(', ')'); } /** diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 8538a97..657516a 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -43,10 +43,8 @@ public function assertExportEquals(string $expected, mixed $var, int $options = // if the exported value is a closure with no parameters, test that the exported closure returns the same // value as the original closure - if ($var instanceof \Closure) { - if ((new \ReflectionFunction($var))->getNumberOfRequiredParameters() === 0) { - $this->assertSame($var(), ($exportedVar()), 'The exported closure does not return the same value as the original closure.'); - } + if ($var instanceof \Closure && (new \ReflectionFunction($var))->getNumberOfRequiredParameters() === 0) { + $this->assertSame($var(), ($exportedVar()), 'The exported closure does not return the same value as the original closure.'); } } From af98bfc2b702a312abbcaff37656dbe419cec5bc Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 20 Feb 2025 18:42:39 +0100 Subject: [PATCH 113/129] Prepare for release --- CHANGELOG.md | 12 ++++++++++++ README.md | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfee8bb..762b4fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [0.6.0](https://github.com/brick/varexporter/releases/tag/0.6.0) - 2025-02-20 + +💥 **BC breaks** + +- Minimum PHP version is now `8.1` (#39) + +✨ **New features** + +- Support for exporting `match` constructs in closures (#38) + +Thanks to @reinfi! + ## [0.5.0](https://github.com/brick/varexporter/releases/tag/0.5.0) - 2024-05-10 ✨ **Compatibility** diff --git a/README.md b/README.md index 8c8927e..5ac3b4f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ composer require brick/varexporter This library requires PHP 8.1 or later. -For PHP 7.4, you can use version `0.5`. For PHP 7.2 & 7.3 compatibility, you can use version `0.3`. Note that [these PHP versions are EOL](http://php.net/supported-versions.php) and not supported anymore. If you're still using one of these PHP versions, you should consider upgrading as soon as possible. +For PHP 7.4 and PHP 8.0 compatibility, you can use version `0.5`. For PHP 7.2 & 7.3, you can use version `0.3`. Note that [these PHP versions are EOL](http://php.net/supported-versions.php) and not supported anymore. If you're still using one of these PHP versions, you should consider upgrading as soon as possible. ### Project status & release process @@ -51,7 +51,7 @@ The current releases are numbered `0.x.y`. When a non-breaking change is introdu **When a breaking change is introduced, a new `0.x` version cycle is always started.** -It is therefore safe to lock your project to a given release cycle, such as `0.5.*`. +It is therefore safe to lock your project to a given release cycle, such as `0.6.*`. If you need to upgrade to a newer release cycle, check the [release history](https://github.com/brick/varexporter/releases) for a list of changes introduced by each further `0.x.0` version. From eaff1381f5252740f27dcb89cb21f898d31a3ab5 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Fri, 29 Aug 2025 18:20:23 +0200 Subject: [PATCH 114/129] Add brick/coding-standard --- .github/workflows/ci.yml | 6 ++++++ .gitignore | 4 ++++ tools/ecs/composer.json | 10 ++++++++++ tools/ecs/ecs.php | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 tools/ecs/composer.json create mode 100644 tools/ecs/ecs.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72d70ff..9cb6275 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,12 @@ jobs: - name: Run Psalm run: vendor/bin/psalm --show-info=false --no-progress + coding-standard: + name: Coding Standard + uses: brick/coding-standard/.github/workflows/coding-standard.yml@v2 + with: + working-directory: "tools/ecs" + phpunit: name: PHPUnit runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 8ae88e5..d9ac457 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ /vendor /composer.lock /.phpunit.cache + +/tools/* +!/tools/ecs/composer.json +!/tools/ecs/ecs.php diff --git a/tools/ecs/composer.json b/tools/ecs/composer.json new file mode 100644 index 0000000..e70d633 --- /dev/null +++ b/tools/ecs/composer.json @@ -0,0 +1,10 @@ +{ + "require": { + "brick/coding-standard": "v2" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": false + } + } +} diff --git a/tools/ecs/ecs.php b/tools/ecs/ecs.php new file mode 100644 index 0000000..9f00ee7 --- /dev/null +++ b/tools/ecs/ecs.php @@ -0,0 +1,32 @@ +import(__DIR__ . '/vendor/brick/coding-standard/ecs.php'); + + $libRootPath = realpath(__DIR__ . '/../../'); + + $ecsConfig->paths( + [ + $libRootPath . '/src', + $libRootPath . '/tests', + __FILE__, + ], + ); + + $ecsConfig->skip([ + // uses unknown functions etc., let's not touch it + $libRootPath . '/tests/ExportClosureTest.php', + + // tests expect a certain order of class elements + OrderedClassElementsFixer::class => $libRootPath . '/tests/Classes/Hierarchy/*.php', + + // assertEquals() is used intentionally in assertExportEquals() + PhpUnitStrictFixer::class => $libRootPath . 'tests/AbstractTestCase.php', + ]); +}; From 219ae588a0606b4e86033bf3c8ecf51963194345 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sat, 6 Sep 2025 23:20:34 +0200 Subject: [PATCH 115/129] Apply coding standard --- src/ExportException.php | 7 +- src/Internal/GenericExporter.php | 110 ++++++++++-------- src/Internal/ObjectExporter.php | 24 ++-- .../ObjectExporter/AnyObjectExporter.php | 27 +++-- .../ObjectExporter/ClosureExporter.php | 60 ++++++---- .../ClosureExporter/PrettyPrinter.php | 6 +- src/Internal/ObjectExporter/EnumExporter.php | 9 +- .../ObjectExporter/InternalClassExporter.php | 5 +- .../ObjectExporter/SerializeExporter.php | 7 +- .../ObjectExporter/SetStateExporter.php | 26 +++-- .../ObjectExporter/StdClassExporter.php | 8 +- src/VarExporter.php | 20 +++- tests/AbstractTestCase.php | 19 ++- tests/Classes/Hierarchy/A.php | 4 +- tests/Classes/Hierarchy/B.php | 2 + tests/Classes/Hierarchy/C.php | 2 + tests/Classes/PrivateConstructor.php | 2 +- tests/Classes/PublicAndPrivateProperties.php | 4 +- tests/Classes/PublicPropertiesOnly.php | 1 + ...icReadonlyPropertiesWithoutConstructor.php | 2 + .../ReadonlyPropertiesWithConstructor.php | 2 +- tests/Classes/SerializeMagicMethods.php | 7 +- tests/Classes/SetState.php | 6 +- tests/ExportObjectTest.php | 37 +++--- tests/VarExporterTest.php | 64 +++++----- 25 files changed, 279 insertions(+), 182 deletions(-) diff --git a/src/ExportException.php b/src/ExportException.php index 662c32c..18049a6 100644 --- a/src/ExportException.php +++ b/src/ExportException.php @@ -4,9 +4,12 @@ namespace Brick\VarExporter; +use Exception; use Throwable; -final class ExportException extends \Exception +use function implode; + +final class ExportException extends Exception { /** * @param string[] $path @@ -25,7 +28,7 @@ public function __construct(string $message, array $path, ?Throwable $previous = * * @param string[] $path */ - public static function pathToString(array $path) : string + public static function pathToString(array $path): string { return '[' . implode('][', $path) . ']'; } diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 7757ccd..33d28f6 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -6,6 +6,21 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\VarExporter; +use ReflectionObject; + +use function array_keys; +use function array_merge; +use function array_unshift; +use function count; +use function gettype; +use function implode; +use function is_array; +use function is_object; +use function is_scalar; +use function range; +use function spl_object_id; +use function sprintf; +use function var_export; /** * The main exporter implementation, that handles variables of any type. @@ -18,20 +33,6 @@ */ final class GenericExporter { - /** - * @var ObjectExporter[] - */ - private array $objectExporters = []; - - /** - * The visited objects, to detect circular references. - * - * This is a two-level map of parent object id => child object id => path where the object first appeared. - * - * @var array> - */ - private array $visitedObjects = []; - /** * @psalm-readonly */ @@ -67,6 +68,20 @@ final class GenericExporter */ public int $indentLevel; + /** + * @var ObjectExporter[] + */ + private array $objectExporters = []; + + /** + * The visited objects, to detect circular references. + * + * This is a two-level map of parent object id => child object id => path where the object first appeared. + * + * @var array> + */ + private array $visitedObjects = []; + public function __construct(int $options, int $indentLevel = 0) { $this->objectExporters[] = new ObjectExporter\StdClassExporter($this); @@ -93,12 +108,12 @@ public function __construct(int $options, int $indentLevel = 0) $this->objectExporters[] = new ObjectExporter\AnyObjectExporter($this); } - $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); - $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); - $this->inlineArray = (bool) ($options & VarExporter::INLINE_ARRAY); - $this->inlineScalarList = (bool) ($options & VarExporter::INLINE_SCALAR_LIST); - $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); - $this->trailingCommaInArray = (bool) ($options & VarExporter::TRAILING_COMMA_IN_ARRAY); + $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); + $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); + $this->inlineArray = (bool) ($options & VarExporter::INLINE_ARRAY); + $this->inlineScalarList = (bool) ($options & VarExporter::INLINE_SCALAR_LIST); + $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); + $this->trailingCommaInArray = (bool) ($options & VarExporter::TRAILING_COMMA_IN_ARRAY); $this->indentLevel = $indentLevel; } @@ -112,7 +127,7 @@ public function __construct(int $options, int $indentLevel = 0) * * @throws ExportException */ - public function export(mixed $var, array $path, array $parentIds) : array + public function export(mixed $var, array $path, array $parentIds): array { if ($var === null) { return ['null']; @@ -136,8 +151,6 @@ public function export(mixed $var, array $path, array $parentIds) : array } /** - * @psalm-suppress MixedAssignment - * * @param array $array The array to export. * @param string[] $path The path to the current array in the array/object graph. * @param int[] $parentIds The ids of all objects higher in the graph. @@ -145,8 +158,10 @@ public function export(mixed $var, array $path, array $parentIds) : array * @return string[] The lines of code. * * @throws ExportException + * + * @psalm-suppress MixedAssignment */ - public function exportArray(array $array, array $path, array $parentIds) : array + public function exportArray(array $array, array $path, array $parentIds): array { if (! $array) { return ['[]']; @@ -200,24 +215,6 @@ public function exportArray(array $array, array $path, array $parentIds) : array return $result; } - /** - * Returns whether the given array only contains scalar values. - * - * Types considered scalar here are int, bool, float, string and null. - * If the array is empty, this method returns true. - * - */ - private function isScalarList(array $array) : bool - { - foreach ($array as $value) { - if ($value !== null && ! is_scalar($value)) { - return false; - } - } - - return true; - } - /** * @param object $object The object to export. * @param string[] $path The path to the current object in the array/object graph. @@ -227,7 +224,7 @@ private function isScalarList(array $array) : bool * * @throws ExportException */ - public function exportObject(object $object, array $path, array $parentIds) : array + public function exportObject(object $object, array $path, array $parentIds): array { $id = spl_object_id($object); @@ -237,14 +234,14 @@ public function exportObject(object $object, array $path, array $parentIds) : ar 'Object of class "%s" has a circular reference at %s. ' . 'Circular references are currently not supported.', $object::class, - ExportException::pathToString($this->visitedObjects[$parentId][$id]) + ExportException::pathToString($this->visitedObjects[$parentId][$id]), ), $path); } $this->visitedObjects[$parentId][$id] = $path; } - $reflectionObject = new \ReflectionObject($object); + $reflectionObject = new ReflectionObject($object); foreach ($this->objectExporters as $objectExporter) { if ($objectExporter->supports($reflectionObject)) { @@ -266,9 +263,9 @@ public function exportObject(object $object, array $path, array $parentIds) : ar * * @return string[] The indented lines of code. */ - public function indent(array $lines) : array + public function indent(array $lines): array { - foreach ($lines as & $value) { + foreach ($lines as &$value) { if ($value !== '') { $value = ' ' . $value; } @@ -284,11 +281,28 @@ public function indent(array $lines) : array * * @return string[] */ - public function wrap(array $lines, string $prepend, string $append) : array + public function wrap(array $lines, string $prepend, string $append): array { $lines[0] = $prepend . $lines[0]; $lines[count($lines) - 1] .= $append; return $lines; } + + /** + * Returns whether the given array only contains scalar values. + * + * Types considered scalar here are int, bool, float, string and null. + * If the array is empty, this method returns true. + */ + private function isScalarList(array $array): bool + { + foreach ($array as $value) { + if ($value !== null && ! is_scalar($value)) { + return false; + } + } + + return true; + } } diff --git a/src/Internal/ObjectExporter.php b/src/Internal/ObjectExporter.php index d3e7eee..9497a3c 100644 --- a/src/Internal/ObjectExporter.php +++ b/src/Internal/ObjectExporter.php @@ -5,6 +5,10 @@ namespace Brick\VarExporter\Internal; use Brick\VarExporter\ExportException; +use ReflectionClass; +use ReflectionObject; + +use function array_merge; /** * An exporter that handles a specific type of object. @@ -23,23 +27,23 @@ public function __construct(GenericExporter $exporter) /** * Returns whether this exporter supports the given object. * - * @param \ReflectionObject $reflectionObject A reflection of the object. + * @param ReflectionObject $reflectionObject A reflection of the object. */ - abstract public function supports(\ReflectionObject $reflectionObject) : bool; + abstract public function supports(ReflectionObject $reflectionObject): bool; /** * Exports the given object. * - * @param object $object The object to export. - * @param \ReflectionObject $reflectionObject A reflection of the object. - * @param string[] $path The path to the current object in the array/object graph. - * @param int[] $parentIds The ids of all objects higher in the graph. + * @param object $object The object to export. + * @param ReflectionObject $reflectionObject A reflection of the object. + * @param string[] $path The path to the current object in the array/object graph. + * @param int[] $parentIds The ids of all objects higher in the graph. * * @return string[] The lines of code. * * @throws ExportException */ - abstract public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array; + abstract public function export(object $object, ReflectionObject $reflectionObject, array $path, array $parentIds): array; /** * Returns the code to create a new object of the given class. @@ -48,7 +52,7 @@ abstract public function export(object $object, \ReflectionObject $reflectionObj * * @return string[] The lines of code. */ - final protected function getCreateObjectCode(\ReflectionClass $class) : array + final protected function getCreateObjectCode(ReflectionClass $class): array { $className = '\\' . $class->getName(); @@ -75,12 +79,12 @@ final protected function getCreateObjectCode(\ReflectionClass $class) : array * * @return string[] The lines of code, wrapped in a closure. */ - final protected function wrapInClosure(array $code) : array + final protected function wrapInClosure(array $code): array { return array_merge( ['(static function() {'], $this->exporter->indent($code), - ['})()'] + ['})()'], ); } } diff --git a/src/Internal/ObjectExporter/AnyObjectExporter.php b/src/Internal/ObjectExporter/AnyObjectExporter.php index 573d6c3..2ab0416 100644 --- a/src/Internal/ObjectExporter/AnyObjectExporter.php +++ b/src/Internal/ObjectExporter/AnyObjectExporter.php @@ -6,6 +6,16 @@ use Brick\VarExporter\Internal\ObjectExporter; use Override; +use ReflectionClass; +use ReflectionObject; +use ReflectionProperty; + +use function array_key_exists; +use function array_merge; +use function method_exists; +use function preg_match; +use function spl_object_id; +use function var_export; /** * Handles any class through direct property access and bound closures. @@ -18,7 +28,7 @@ final class AnyObjectExporter extends ObjectExporter { #[Override] - public function supports(\ReflectionObject $reflectionObject) : bool + public function supports(ReflectionObject $reflectionObject): bool { return true; } @@ -27,15 +37,15 @@ public function supports(\ReflectionObject $reflectionObject) : bool * @psalm-suppress MixedAssignment */ #[Override] - public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, ReflectionObject $reflectionObject, array $path, array $parentIds): array { $lines = $this->getCreateObjectCode($reflectionObject); $objectAsArray = (array) $object; $current = $this->exporter->skipDynamicProperties - ? new \ReflectionClass($object) // properties from class definition only - : $reflectionObject; // properties from class definition + dynamic properties + ? new ReflectionClass($object) // properties from class definition only + : $reflectionObject; // properties from class definition + dynamic properties $isParentClass = false; @@ -68,13 +78,13 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra if (array_key_exists($key, $objectAsArray)) { $value = $objectAsArray[$key]; - if ($property->isPublic() && !(method_exists($property, 'isReadOnly') && $property->isReadOnly())) { + if ($property->isPublic() && ! (method_exists($property, 'isReadOnly') && $property->isReadOnly())) { $publicNonReadonlyProperties[$name] = $value; } else { $nonPublicOrPublicReadonlyProperties[$name] = $value; } } else { - if ($property->isPublic() && !(method_exists($property, 'isReadOnly') && $property->isReadOnly())) { + if ($property->isPublic() && ! (method_exists($property, 'isReadOnly') && $property->isReadOnly())) { $unsetPublicNonReadonlyProperties[] = $name; } else { $unsetNonPublicOrPublicReadonlyProperties[] = $name; @@ -153,9 +163,8 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra /** * Returns the key of the given property in the object-to-array cast. - * */ - private function getPropertyKey(\ReflectionProperty $property) : string + private function getPropertyKey(ReflectionProperty $property): string { $name = $property->getName(); @@ -170,7 +179,7 @@ private function getPropertyKey(\ReflectionProperty $property) : string return $name; } - private function escapePropName(string $var) : string + private function escapePropName(string $var): string { if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $var) === 1) { return $var; diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 1e6120e..047cfb6 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -15,8 +15,20 @@ use PhpParser\NodeVisitor\NameResolver; use PhpParser\Parser; use PhpParser\ParserFactory; -use PhpParser\PhpVersion; use ReflectionFunction; +use ReflectionObject; + +use function array_keys; +use function array_merge; +use function assert; +use function count; +use function file_get_contents; +use function implode; +use function is_string; +use function sprintf; +use function str_ends_with; + +use const PHP_EOL; /** * Handles closures. @@ -28,17 +40,17 @@ final class ClosureExporter extends ObjectExporter private ?Parser $parser = null; #[Override] - public function supports(\ReflectionObject $reflectionObject) : bool + public function supports(ReflectionObject $reflectionObject): bool { - return $reflectionObject->getName() === \Closure::class; + return $reflectionObject->getName() === Closure::class; } #[Override] - public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, ReflectionObject $reflectionObject, array $path, array $parentIds): array { assert($object instanceof Closure); - $reflectionFunction = new \ReflectionFunction($object); + $reflectionFunction = new ReflectionFunction($object); $file = $reflectionFunction->getFileName(); $line = $reflectionFunction->getStartLine(); @@ -62,7 +74,7 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra private function getParser(): Parser { if ($this->parser === null) { - $this->parser = (new ParserFactory)->createForHostVersion(); + $this->parser = (new ParserFactory())->createForHostVersion(); } return $this->parser; @@ -78,13 +90,13 @@ private function getParser(): Parser * * @throws ExportException */ - private function parseFile(string $filename, array $path) : array + private function parseFile(string $filename, array $path): array { if (str_ends_with($filename, " : eval()'d code")) { throw new ExportException("Closure defined in eval()'d code cannot be exported.", $path); } - $source = @ file_get_contents($filename); + $source = @file_get_contents($filename); if ($source === false) { // @codeCoverageIgnoreStart @@ -113,7 +125,7 @@ private function parseFile(string $filename, array $path) : array * * @return Node[] */ - private function resolveNames(array $ast) : array + private function resolveNames(array $ast): array { $nameResolver = new NameResolver(); $nodeTraverser = new NodeTraverser(); @@ -138,11 +150,11 @@ private function getClosure( array $ast, string $file, int $line, - array $path - ) : Node\Expr\Closure { + array $path, + ): Node\Expr\Closure { $finder = new FindingVisitor( - fn(Node $node): bool => ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) - && $node->getStartLine() === $line + fn (Node $node): bool => ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) + && $node->getStartLine() === $line, ); $traverser = new NodeTraverser(); @@ -157,11 +169,11 @@ private function getClosure( 'Expected exactly 1 closure in %s on line %d, found %d.', $file, $line, - $count + $count, ), $path); } - /** @var Node\Expr\Closure|Node\Expr\ArrowFunction $closure */ + /** @var Node\Expr\ArrowFunction|Node\Expr\Closure $closure */ $closure = $closures[0]; if ($closure instanceof Node\Expr\ArrowFunction) { @@ -178,13 +190,13 @@ private function getClosure( /** * Convert a parsed arrow function to a closure. * - * @param ReflectionFunction $reflectionFunction Reflection of the closure. - * @param Node\Expr\ArrowFunction $arrowFunction Parsed arrow function. + * @param ReflectionFunction $reflectionFunction Reflection of the closure. + * @param Node\Expr\ArrowFunction $arrowFunction Parsed arrow function. */ private function convertArrowFunction( ReflectionFunction $reflectionFunction, - Node\Expr\ArrowFunction $arrowFunction - ) : Node\Expr\Closure { + Node\Expr\ArrowFunction $arrowFunction, + ): Node\Expr\Closure { $closure = new Node\Expr\Closure([], ['arrow_function' => true]); $closure->static = false; @@ -199,7 +211,7 @@ private function convertArrowFunction( assert(is_string($var)); $closure->uses[] = new Node\Expr\ClosureUse( - new Node\Expr\Variable($var) + new Node\Expr\Variable($var), ); } @@ -218,11 +230,11 @@ private function convertArrowFunction( private function closureHandleUses( ReflectionFunction $reflectionFunction, Node\Expr\Closure $closure, - array $path - ) : void { + array $path, + ): void { if (! $this->exporter->closureSnapshotUses) { $message = $closure->hasAttribute('arrow_function') - ? "The arrow function uses variables in the parent scope, this is not supported by default" + ? 'The arrow function uses variables in the parent scope, this is not supported by default' : "The closure has bound variables through 'use', this is not supported by default"; throw new ExportException("$message. Use the CLOSURE_SNAPSHOT_USE option to export them.", $path); @@ -249,7 +261,7 @@ private function closureHandleUses( $assign = new Node\Expr\Assign( new Node\Expr\Variable($var), - $expr->expr + $expr->expr, ); $stmts[] = new Node\Stmt\Expression($assign); } diff --git a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php index 97fd442..7b89289 100644 --- a/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php +++ b/src/Internal/ObjectExporter/ClosureExporter/PrettyPrinter.php @@ -7,6 +7,8 @@ use Override; use PhpParser\PrettyPrinter\Standard; +use function str_repeat; + /** * Extends the standard pretty-printer to allow for a base indent level. * @@ -16,13 +18,13 @@ final class PrettyPrinter extends Standard { private int $varExporterNestingLevel = 0; - public function setVarExporterNestingLevel(int $level) : void + public function setVarExporterNestingLevel(int $level): void { $this->varExporterNestingLevel = $level; } #[Override] - protected function resetState() : void + protected function resetState(): void { parent::resetState(); diff --git a/src/Internal/ObjectExporter/EnumExporter.php b/src/Internal/ObjectExporter/EnumExporter.php index 86fc1cb..77cda93 100644 --- a/src/Internal/ObjectExporter/EnumExporter.php +++ b/src/Internal/ObjectExporter/EnumExporter.php @@ -6,8 +6,11 @@ use Brick\VarExporter\Internal\ObjectExporter; use Override; +use ReflectionObject; use UnitEnum; +use function assert; + /** * Handles enums. * @@ -16,18 +19,18 @@ final class EnumExporter extends ObjectExporter { #[Override] - public function supports(\ReflectionObject $reflectionObject) : bool + public function supports(ReflectionObject $reflectionObject): bool { return $reflectionObject->isEnum(); } #[Override] - public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, ReflectionObject $reflectionObject, array $path, array $parentIds): array { assert($object instanceof UnitEnum); return [ - $object::class . '::' . $object->name + $object::class . '::' . $object->name, ]; } } diff --git a/src/Internal/ObjectExporter/InternalClassExporter.php b/src/Internal/ObjectExporter/InternalClassExporter.php index 79ce7cf..62be313 100644 --- a/src/Internal/ObjectExporter/InternalClassExporter.php +++ b/src/Internal/ObjectExporter/InternalClassExporter.php @@ -7,6 +7,7 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; use Override; +use ReflectionObject; /** * Throws on internal classes. @@ -16,13 +17,13 @@ final class InternalClassExporter extends ObjectExporter { #[Override] - public function supports(\ReflectionObject $reflectionObject) : bool + public function supports(ReflectionObject $reflectionObject): bool { return $reflectionObject->isInternal(); } #[Override] - public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, ReflectionObject $reflectionObject, array $path, array $parentIds): array { $className = $reflectionObject->getName(); diff --git a/src/Internal/ObjectExporter/SerializeExporter.php b/src/Internal/ObjectExporter/SerializeExporter.php index 6953ae8..639ac40 100644 --- a/src/Internal/ObjectExporter/SerializeExporter.php +++ b/src/Internal/ObjectExporter/SerializeExporter.php @@ -6,6 +6,9 @@ use Brick\VarExporter\Internal\ObjectExporter; use Override; +use ReflectionObject; + +use function array_merge; /** * Handles instances of classes with __serialize() and __unserialize() methods. @@ -15,14 +18,14 @@ final class SerializeExporter extends ObjectExporter { #[Override] - public function supports(\ReflectionObject $reflectionObject) : bool + public function supports(ReflectionObject $reflectionObject): bool { return $reflectionObject->hasMethod('__serialize') && $reflectionObject->hasMethod('__unserialize'); } #[Override] - public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, ReflectionObject $reflectionObject, array $path, array $parentIds): array { $lines = $this->getCreateObjectCode($reflectionObject); diff --git a/src/Internal/ObjectExporter/SetStateExporter.php b/src/Internal/ObjectExporter/SetStateExporter.php index a33c79e..d760c21 100644 --- a/src/Internal/ObjectExporter/SetStateExporter.php +++ b/src/Internal/ObjectExporter/SetStateExporter.php @@ -7,6 +7,12 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\Internal\ObjectExporter; use Override; +use ReflectionClass; +use ReflectionObject; + +use function array_key_exists; +use function strrpos; +use function substr; /** * Handles instances of classes with a __set_state() method. @@ -16,7 +22,7 @@ final class SetStateExporter extends ObjectExporter { #[Override] - public function supports(\ReflectionObject $reflectionObject) : bool + public function supports(ReflectionObject $reflectionObject): bool { if ($reflectionObject->hasMethod('__set_state')) { $method = $reflectionObject->getMethod('__set_state'); @@ -28,7 +34,7 @@ public function supports(\ReflectionObject $reflectionObject) : bool } #[Override] - public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, ReflectionObject $reflectionObject, array $path, array $parentIds): array { $className = $reflectionObject->getName(); @@ -36,7 +42,7 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra $exportedVars = $this->exporter->exportArray($vars, $path, $parentIds); - return $this->exporter->wrap($exportedVars, '\\' . $className . '::__set_state(', ')'); + return $this->exporter->wrap($exportedVars, '\\' . $className . '::__set_state(', ')'); } /** @@ -51,16 +57,16 @@ public function export(object $object, \ReflectionObject $reflectionObject, arra * * This way we offer a better safety guarantee, while staying compatible with var_export() in the output. * - * @psalm-suppress MixedAssignment - * * @param object $object The object to dump. * @param string[] $path The path to the object, in the array/object graph. * * @return array An associative array of property name to value. * * @throws ExportException + * + * @psalm-suppress MixedAssignment */ - private function getObjectVars(object $object, array $path) : array + private function getObjectVars(object $object, array $path): array { $result = []; @@ -78,7 +84,7 @@ private function getObjectVars(object $object, array $path) : array throw new ExportException( 'Class "' . $className . '" has overridden private property "' . $name . '". ' . 'This is not supported for exporting objects with __set_state().', - $path + $path, ); } @@ -92,10 +98,10 @@ private function getObjectVars(object $object, array $path) : array return $result; } - private function isDynamicProperty(object $object, string $name) : bool + private function isDynamicProperty(object $object, string $name): bool { - $reflectionClass = new \ReflectionClass($object); - $reflectionObject = new \ReflectionObject($object); + $reflectionClass = new ReflectionClass($object); + $reflectionObject = new ReflectionObject($object); return $reflectionObject->hasProperty($name) && ! $reflectionClass->hasProperty($name); } diff --git a/src/Internal/ObjectExporter/StdClassExporter.php b/src/Internal/ObjectExporter/StdClassExporter.php index 5c7e869..6e49dcd 100644 --- a/src/Internal/ObjectExporter/StdClassExporter.php +++ b/src/Internal/ObjectExporter/StdClassExporter.php @@ -6,6 +6,8 @@ use Brick\VarExporter\Internal\ObjectExporter; use Override; +use ReflectionObject; +use stdClass; /** * Handles stdClass objects. @@ -15,13 +17,13 @@ final class StdClassExporter extends ObjectExporter { #[Override] - public function supports(\ReflectionObject $reflectionObject) : bool + public function supports(ReflectionObject $reflectionObject): bool { - return $reflectionObject->getName() === \stdClass::class; + return $reflectionObject->getName() === stdClass::class; } #[Override] - public function export(object $object, \ReflectionObject $reflectionObject, array $path, array $parentIds) : array + public function export(object $object, ReflectionObject $reflectionObject, array $path, array $parentIds): array { $exported = $this->exporter->exportArray((array) $object, $path, $parentIds); diff --git a/src/VarExporter.php b/src/VarExporter.php index 7258f33..fe62c8c 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -6,6 +6,14 @@ use Brick\VarExporter\Internal\GenericExporter; +use function array_map; +use function array_shift; +use function count; +use function implode; +use function str_repeat; + +use const PHP_EOL; + final class VarExporter { /** @@ -75,14 +83,14 @@ final class VarExporter public const INLINE_ARRAY = 1 << 11; /** - * @param mixed $var The variable to export. - * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. - * Combine multiple options with a bitwise OR `|` operator. - * @param int $indentLevel The base output indentation level. + * @param mixed $var The variable to export. + * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. + * Combine multiple options with a bitwise OR `|` operator. + * @param int $indentLevel The base output indentation level. * * @throws ExportException */ - public static function export(mixed $var, int $options = 0, int $indentLevel = 0) : string + public static function export(mixed $var, int $options = 0, int $indentLevel = 0): string { $exporter = new GenericExporter($options, $indentLevel); $lines = $exporter->export($var, [], []); @@ -92,7 +100,7 @@ public static function export(mixed $var, int $options = 0, int $indentLevel = 0 } else { $firstLine = array_shift($lines); $lines = array_map( - fn($line) => str_repeat(' ', $indentLevel) . $line, + fn ($line) => str_repeat(' ', $indentLevel) . $line, $lines, ); diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php index 657516a..8f8c4a9 100644 --- a/tests/AbstractTestCase.php +++ b/tests/AbstractTestCase.php @@ -6,7 +6,14 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\VarExporter; +use Closure; use PHPUnit\Framework\TestCase; +use ReflectionFunction; + +use function array_map; +use function explode; +use function implode; +use function preg_quote; abstract class AbstractTestCase extends TestCase { @@ -17,7 +24,7 @@ abstract class AbstractTestCase extends TestCase * @param mixed $var The variable to export. * @param int $options The options to pass to export(). */ - public function assertExportEquals(string $expected, mixed $var, int $options = 0) : void + public function assertExportEquals(string $expected, mixed $var, int $options = 0): void { // test the string output of export() @@ -37,14 +44,14 @@ public function assertExportEquals(string $expected, mixed $var, int $options = // only test if SKIP_DYNAMIC_PROPERTIES is not set, as this might create a non-equal object if (0 === ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES)) { - $this->assertEquals($var, $exportedVar, 'The eval()ed exported var is different from the original var.'); + self::assertEquals($var, $exportedVar, 'The eval()ed exported var is different from the original var.'); } // if the exported value is a closure with no parameters, test that the exported closure returns the same // value as the original closure - if ($var instanceof \Closure && (new \ReflectionFunction($var))->getNumberOfRequiredParameters() === 0) { - $this->assertSame($var(), ($exportedVar()), 'The exported closure does not return the same value as the original closure.'); + if ($var instanceof Closure && (new ReflectionFunction($var))->getNumberOfRequiredParameters() === 0) { + self::assertSame($var(), ($exportedVar()), 'The exported closure does not return the same value as the original closure.'); } } @@ -55,9 +62,9 @@ public function assertExportEquals(string $expected, mixed $var, int $options = * @param mixed $var The variable to export. * @param int $options The options to pass to export(). */ - public function assertExportThrows(string $expectedMessage, mixed $var, int $options = 0) : void + public function assertExportThrows(string $expectedMessage, mixed $var, int $options = 0): void { - $expectedMessageRegExp = '/' . implode('.*', array_map(fn(string $str) => preg_quote($str, '/'), explode('*', $expectedMessage))) . '/'; + $expectedMessageRegExp = '/' . implode('.*', array_map(fn (string $str) => preg_quote($str, '/'), explode('*', $expectedMessage))) . '/'; $this->expectException(ExportException::class); $this->expectExceptionMessageMatches($expectedMessageRegExp); diff --git a/tests/Classes/Hierarchy/A.php b/tests/Classes/Hierarchy/A.php index 62b561d..7f4d5a8 100644 --- a/tests/Classes/Hierarchy/A.php +++ b/tests/Classes/Hierarchy/A.php @@ -7,7 +7,9 @@ abstract class A { private $privateInA = 'private in A'; + protected $protectedInA = 'protected in A'; + public $publicInA = 'public in A'; private $privateOverridden = 'in A'; @@ -16,7 +18,7 @@ private function __construct() { } - public static function create() : self + public static function create(): self { return new static(); } diff --git a/tests/Classes/Hierarchy/B.php b/tests/Classes/Hierarchy/B.php index 00949a3..847a0d6 100644 --- a/tests/Classes/Hierarchy/B.php +++ b/tests/Classes/Hierarchy/B.php @@ -7,7 +7,9 @@ class B extends A { private $privateInB = 'private in B'; + protected $protectedInB = 'protected in B'; + public $publicInB = 'public in B'; // parent has a property with same name diff --git a/tests/Classes/Hierarchy/C.php b/tests/Classes/Hierarchy/C.php index acbf1a8..61577e4 100644 --- a/tests/Classes/Hierarchy/C.php +++ b/tests/Classes/Hierarchy/C.php @@ -7,7 +7,9 @@ class C extends B { private $privateInC = 'private in C'; + protected $protectedInC = 'protected in C'; + public $publicInC = 'public in C'; // parent has a property with same name diff --git a/tests/Classes/PrivateConstructor.php b/tests/Classes/PrivateConstructor.php index cd2c71e..422380e 100644 --- a/tests/Classes/PrivateConstructor.php +++ b/tests/Classes/PrivateConstructor.php @@ -12,6 +12,6 @@ private function __construct() public static function create() { - return new self; + return new self(); } } diff --git a/tests/Classes/PublicAndPrivateProperties.php b/tests/Classes/PublicAndPrivateProperties.php index 327d35c..d23ae21 100644 --- a/tests/Classes/PublicAndPrivateProperties.php +++ b/tests/Classes/PublicAndPrivateProperties.php @@ -8,12 +8,12 @@ class PublicAndPrivateProperties extends PublicPropertiesOnly { private string $baz = 'defaultValue'; - protected function setBaz($baz) : void + protected function setBaz($baz): void { $this->baz = $baz; } - protected function unsetBaz() : void + protected function unsetBaz(): void { unset($this->baz); } diff --git a/tests/Classes/PublicPropertiesOnly.php b/tests/Classes/PublicPropertiesOnly.php index fbd3f73..23b6d7d 100644 --- a/tests/Classes/PublicPropertiesOnly.php +++ b/tests/Classes/PublicPropertiesOnly.php @@ -10,5 +10,6 @@ class PublicPropertiesOnly { public $foo; + public $bar; } diff --git a/tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php b/tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php index 46ae1d5..a47349c 100644 --- a/tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php +++ b/tests/Classes/PublicReadonlyPropertiesWithoutConstructor.php @@ -7,6 +7,8 @@ class PublicReadonlyPropertiesWithoutConstructor { public readonly string $foo; + private readonly string $bar; + public string $baz; } diff --git a/tests/Classes/ReadonlyPropertiesWithConstructor.php b/tests/Classes/ReadonlyPropertiesWithConstructor.php index 84e22b8..80ee789 100644 --- a/tests/Classes/ReadonlyPropertiesWithConstructor.php +++ b/tests/Classes/ReadonlyPropertiesWithConstructor.php @@ -9,7 +9,7 @@ class ReadonlyPropertiesWithConstructor public function __construct( public readonly string $foo, private readonly string $bar, - public string $baz + public string $baz, ) { } } diff --git a/tests/Classes/SerializeMagicMethods.php b/tests/Classes/SerializeMagicMethods.php index 22be43b..166eeb3 100644 --- a/tests/Classes/SerializeMagicMethods.php +++ b/tests/Classes/SerializeMagicMethods.php @@ -10,17 +10,18 @@ class SerializeMagicMethods { public $foo; + public $bar; - public function __serialize() : array + public function __serialize(): array { return [ 'foo' => $this->foo, - 'bar' => $this->bar + 'bar' => $this->bar, ]; } - public function __unserialize(array $array) : void + public function __unserialize(array $array): void { $this->foo = $array['foo']; $this->bar = $array['bar']; diff --git a/tests/Classes/SetState.php b/tests/Classes/SetState.php index 5773079..76ceb71 100644 --- a/tests/Classes/SetState.php +++ b/tests/Classes/SetState.php @@ -4,11 +4,13 @@ namespace Brick\VarExporter\Tests\Classes; +use function array_key_exists; + class SetState extends PublicAndPrivateProperties { - public static function __set_state(array $array) : self + public static function __set_state(array $array): self { - $object = new self; + $object = new self(); // note: these checks would usually not be necessary in a typical app; they're just here because // we need to test our implementation against edge cases, such as unset properties. diff --git a/tests/ExportObjectTest.php b/tests/ExportObjectTest.php index e502416..961638e 100644 --- a/tests/ExportObjectTest.php +++ b/tests/ExportObjectTest.php @@ -11,13 +11,14 @@ use Brick\VarExporter\Tests\Classes\PrivateConstructor; use Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; use Brick\VarExporter\Tests\Classes\PublicPropertiesWithConstructor; -use Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor; use Brick\VarExporter\Tests\Classes\PublicReadonlyPropertiesWithoutConstructor; +use Brick\VarExporter\Tests\Classes\ReadonlyPropertiesWithConstructor; use Brick\VarExporter\Tests\Classes\SerializeMagicMethods; use Brick\VarExporter\Tests\Classes\SerializeMagicMethodsWithConstructor; use Brick\VarExporter\Tests\Classes\SetState; use Brick\VarExporter\Tests\Classes\SetStateWithOverriddenPrivateProperties; use Brick\VarExporter\VarExporter; +use stdClass; /** * Tests exporting various objects. @@ -26,10 +27,10 @@ class ExportObjectTest extends AbstractTestCase { public function testExportStdClass(): void { - $object = new \stdClass; + $object = new stdClass(); $object->foo = 'Hello'; $object->bar = 'bar'; - $object->baz = new \stdClass; + $object->baz = new stdClass(); $object->baz->foo = 'Hello'; $expected = <<<'PHP' @@ -47,7 +48,7 @@ public function testExportStdClass(): void public function testExportClassWithNoProperties(): void { - $object = new NoProperties; + $object = new NoProperties(); $expected = 'new \Brick\VarExporter\Tests\Classes\NoProperties'; @@ -56,7 +57,7 @@ public function testExportClassWithNoProperties(): void public function testExportClassWithDynamicPropertiesOnly(): void { - $object = new NoProperties; + $object = new NoProperties(); $object->x = 1.0; $object->dynamicProp = 'Hello'; $object->{'$weird%Prop'} = 'World'; @@ -80,7 +81,7 @@ public function testExportClassWithDynamicPropertiesOnly(): void public function testExportClassWithDynamicPropertiesOnly_SkipDynamicProperties(): void { - $object = new NoProperties; + $object = new NoProperties(); $object->dynamicProp = 'Hello'; $object->{'$weird%Prop'} = 'World'; @@ -107,7 +108,7 @@ public function testExportClassWithConstructorAndNoProperties(): void public function testExportClassWithPublicPropertiesOnly(): void { - $object = new PublicPropertiesOnly; + $object = new PublicPropertiesOnly(); $object->foo = 'Hello'; $object->bar = 'World'; @@ -127,7 +128,7 @@ public function testExportClassWithPublicPropertiesOnly(): void public function testExportObjectWithPublicAndDynamicProperties(): void { - $object = new PublicPropertiesOnly; + $object = new PublicPropertiesOnly(); $object->foo = 'Hello'; $object->bar = 'World'; $object->dynamic = 'Dynamic'; @@ -149,7 +150,7 @@ public function testExportObjectWithPublicAndDynamicProperties(): void public function testExportObjectWithPublicAndDynamicProperties_SkipDynamicProperties(): void { - $object = new PublicPropertiesOnly; + $object = new PublicPropertiesOnly(); $object->foo = 'Hello'; $object->bar = 'World'; $object->dynamic = 'Dynamic'; @@ -170,7 +171,7 @@ public function testExportObjectWithPublicAndDynamicProperties_SkipDynamicProper public function testExportClassWithSetState(): void { - $object = new SetState; + $object = new SetState(); $object->foo = 'Hello'; $object->bar = 'World'; @@ -187,7 +188,7 @@ public function testExportClassWithSetState(): void public function testExportClassWithSetStateAndUnsetProperties(): void { - $object = new SetState; + $object = new SetState(); $object->foo = null; unset($object->bar); @@ -204,7 +205,7 @@ public function testExportClassWithSetStateAndUnsetProperties(): void public function testExportClassWithSetStateAndDynamicProperties(): void { - $object = new SetState; + $object = new SetState(); $object->foo = 'Hello'; $object->bar = 'World'; $object->dynamic = 'Dynamic property'; @@ -225,7 +226,7 @@ public function testExportClassWithSetStateAndDynamicProperties(): void public function testExportClassWithSetStateAndDynamicProperties_SkipDynamicProperties(): void { - $object = new SetState; + $object = new SetState(); $object->foo = 'Hello'; $object->bar = 'World'; $object->dynamic = 'Dynamic property'; @@ -243,7 +244,7 @@ public function testExportClassWithSetStateAndDynamicProperties_SkipDynamicPrope public function testExportClassWithSetStateAndOverriddenPrivateProperties(): void { - $object = new SetStateWithOverriddenPrivateProperties; + $object = new SetStateWithOverriddenPrivateProperties(); $expectedMessage = 'Class "Brick\VarExporter\Tests\Classes\SetStateWithOverriddenPrivateProperties" has overridden private property "baz". This is not supported for exporting objects with __set_state().'; @@ -323,7 +324,7 @@ public function testExportClassWithStateAndReadonlyPublicProperties(): void { $object = new PublicReadonlyPropertiesWithoutConstructor(); - (function () { + (function (): void { $this->foo = 'foo'; })->bindTo($object, PublicReadonlyPropertiesWithoutConstructor::class)(); @@ -347,7 +348,7 @@ public function testExportClassWithStateAndReadonlyPublicProperties(): void public function testExportClassWithSerializeMagicMethods(): void { - $object = new SerializeMagicMethods; + $object = new SerializeMagicMethods(); $object->foo = 'Foo'; $object->bar = [1, 2]; @@ -506,13 +507,13 @@ public function testExportClassHierarchyWithUnsetProperties(): void $object->publicInA = null; unset($object->publicInB); - (function() { + (function (): void { /** @var Hierarchy\C $this */ unset($this->privateInC); unset($this->protectedInB); })->bindTo($object, Hierarchy\C::class)(); - (function() { + (function (): void { /** @var Hierarchy\A $this */ unset($this->privateOverridden); })->bindTo($object, Hierarchy\A::class)(); diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index a4fb718..61f2ac5 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -4,11 +4,21 @@ namespace Brick\VarExporter\Tests; +use ArrayIterator; use Brick\VarExporter\ExportException; use Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; use Brick\VarExporter\Tests\Classes\SetState; use Brick\VarExporter\VarExporter; +use DateTime; +use DateTimeImmutable; +use DateTimeZone; use PHPUnit\Framework\Attributes\DataProvider; +use stdClass; + +use function fopen; +use function str_replace; + +use const PHP_EOL; class VarExporterTest extends AbstractTestCase { @@ -28,29 +38,29 @@ public function testMixedVar(): void 'aFloat' => 0.75, 'anInt' => 123, 'aNumericArray' => ['a', 'b', null, [ - 'c' => 'd' + 'c' => 'd', ]], 'anAssociativeArray' => [ 'a' => 'b', 'c' => [ 'd' => 'e', 'f' => [ - 'g' => [[]] - ] - ] + 'g' => [[]], + ], + ], ], 'anObject' => (object) [ 'type' => 'string', '$ref' => '#/components/schema/User', 'items' => (object) [ 'foo' => 'bar', - 'empty' => (object) [] - ] + 'empty' => (object) [], + ], ], - 'aCustomObject' => $myObject + 'aCustomObject' => $myObject, ]; - $expected = <<<'PHP' + $expected = <<<'PHP' [ 'aString' => 'Hello', 'aTrue' => true, @@ -105,7 +115,7 @@ public function testMixedVar(): void public function testExportObjectPropWithSpecialChars(): void { - $object = new PublicPropertiesOnly; + $object = new PublicPropertiesOnly(); $object->{'$ref'} = '#/components/schemas/User'; $expected = <<<'PHP' @@ -135,7 +145,7 @@ public function testInlineArray(): void { $var = [ 'one' => ['hello', 'world', 123, true, false, null, 7.5], - 'two' => ['hello', 'world', [1 => 'one', 'two', 'three']] + 'two' => ['hello', 'world', [1 => 'one', 'two', 'three']], ]; $expected = <<<'PHP' @@ -149,7 +159,7 @@ public function testInlineScalarList(): void { $var = [ 'one' => ['hello', 'world', 123, true, false, null, 7.5], - 'two' => ['hello', 'world', ['one', 'two', 'three']] + 'two' => ['hello', 'world', ['one', 'two', 'three']], ]; $expected = <<<'PHP' @@ -170,7 +180,7 @@ public function testTrailingCommaInArray(): void { $var = [ 'one' => ['hello', 'world', 123, true, false, null, 7.5], - 'two' => ['hello', 'world', ['one', 'two', 'three']] + 'two' => ['hello', 'world', ['one', 'two', 'three']], ]; $expected = <<<'PHP' @@ -189,10 +199,10 @@ public function testTrailingCommaInArray(): void public function testExportDateTime(): void { - $timezone = new \DateTimeZone('Europe/Berlin'); + $timezone = new DateTimeZone('Europe/Berlin'); $format = 'Y-m-d H:i:s.u'; - $var = \DateTime::createFromFormat($format, '2020-03-09 18:51:23.000000', $timezone); + $var = DateTime::createFromFormat($format, '2020-03-09 18:51:23.000000', $timezone); $expected = <<<'PHP' \DateTime::__set_state([ @@ -207,10 +217,10 @@ public function testExportDateTime(): void public function testExportDateTimeImmutable(): void { - $timezone = new \DateTimeZone('Europe/Berlin'); + $timezone = new DateTimeZone('Europe/Berlin'); $format = 'Y-m-d H:i:s.u'; - $var = \DateTimeImmutable::createFromFormat($format, '2020-03-10 17:06:19.000000', $timezone); + $var = DateTimeImmutable::createFromFormat($format, '2020-03-10 17:06:19.000000', $timezone); $expected = <<<'PHP' \DateTimeImmutable::__set_state([ @@ -225,8 +235,8 @@ public function testExportDateTimeImmutable(): void public function testExportInternalClass(): void { - $object = new \stdClass; - $object->iterator = new \ArrayIterator(); + $object = new stdClass(); + $object->iterator = new ArrayIterator(); $expectedMessage = 'Class "ArrayIterator" is internal, and cannot be exported.'; @@ -240,8 +250,8 @@ public function testExportResource(): void // bury it deep $object = (object) [ 'foo' => (object) [ - 'bar' => $handle - ] + 'bar' => $handle, + ], ]; $this->expectException(ExportException::class); @@ -252,13 +262,13 @@ public function testExportResource(): void public function testExportObjectTwiceWithoutCircularReference(): void { - $a = new PublicPropertiesOnly; + $a = new PublicPropertiesOnly(); $a->foo = 'Foo'; $a->bar = 'Bar'; $var = [ 'x' => $a, - 'y' => $a + 'y' => $a, ]; $expected = <<<'PHP' @@ -287,8 +297,8 @@ public function testExportObjectTwiceWithoutCircularReference(): void public function testExportObjectWithCircularReference(): void { - $a = new PublicPropertiesOnly; - $b = new PublicPropertiesOnly; + $a = new PublicPropertiesOnly(); + $b = new PublicPropertiesOnly(); $a->foo = $b; $b->foo = $a; @@ -298,8 +308,8 @@ public function testExportObjectWithCircularReference(): void VarExporter::export([ 'x' => [ - 'y' => $a - ] + 'y' => $a, + ], ]); } @@ -316,7 +326,7 @@ public function foo() $exported = VarExporter::export($var, $options, 1); $result = str_replace('{{exported}}', $exported, $template); - $this->assertEquals($expected, $result); + self::assertSame($expected, $result); } public static function providerExportIndented(): iterable From c84d0b5c773936c5f646b060e8b178d0d015fbc5 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Sat, 6 Sep 2025 23:47:55 +0200 Subject: [PATCH 116/129] Update Psalm to version 6.13.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5355475..79a38bc 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^10.5", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "6.8.4" + "vimeo/psalm": "6.13.1" }, "autoload": { "psr-4": { From 990c2ab5d532da2c15cd5c2a5e56bca4f91a34bd Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 19:09:09 +0100 Subject: [PATCH 117/129] Use array_is_list() --- src/Internal/GenericExporter.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 33d28f6..3f57ec0 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -8,7 +8,7 @@ use Brick\VarExporter\VarExporter; use ReflectionObject; -use function array_keys; +use function array_is_list; use function array_merge; use function array_unshift; use function count; @@ -17,7 +17,6 @@ use function is_array; use function is_object; use function is_scalar; -use function range; use function spl_object_id; use function sprintf; use function var_export; @@ -170,7 +169,7 @@ public function exportArray(array $array, array $path, array $parentIds): array $result = []; $count = count($array); - $isList = array_keys($array) === range(0, $count - 1); + $isList = array_is_list($array); $current = 0; From 2602510a3da5bd8769ffd52dbb65525f487dda77 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 19:14:02 +0100 Subject: [PATCH 118/129] Fix deprecation --- src/Internal/ObjectExporter/ClosureExporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Internal/ObjectExporter/ClosureExporter.php b/src/Internal/ObjectExporter/ClosureExporter.php index 047cfb6..1c2ecb7 100644 --- a/src/Internal/ObjectExporter/ClosureExporter.php +++ b/src/Internal/ObjectExporter/ClosureExporter.php @@ -210,7 +210,7 @@ private function convertArrowFunction( foreach (array_keys($static) as $var) { assert(is_string($var)); - $closure->uses[] = new Node\Expr\ClosureUse( + $closure->uses[] = new Node\ClosureUse( new Node\Expr\Variable($var), ); } From f4af6322b590c2cbaae38ba2238441467edf54ee Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 19:14:43 +0100 Subject: [PATCH 119/129] Update Psalm to 6.14.3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 79a38bc..fe402a9 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require-dev": { "phpunit/phpunit": "^10.5", "php-coveralls/php-coveralls": "^2.2", - "vimeo/psalm": "6.13.1" + "vimeo/psalm": "6.14.3" }, "autoload": { "psr-4": { From d85a8b55e16f49a2422f37b0aaeb1be0ee439496 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 19:16:59 +0100 Subject: [PATCH 120/129] Test on PHP 8.5 --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cb6275..e51b4dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,8 @@ on: pull_request: env: - PSALM_PHP_VERSION: "8.4" - COVERAGE_PHP_VERSION: "8.4" + PSALM_PHP_VERSION: "8.5" + COVERAGE_PHP_VERSION: "8.5" jobs: psalm: @@ -46,6 +46,7 @@ jobs: - "8.2" - "8.3" - "8.4" + - "8.5" deps: - "highest" include: From dea3239ccdf14dc102aaf511b54c37cf6a46e444 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 19:26:49 +0100 Subject: [PATCH 121/129] Add VarExporter::INLINE_LITERAL_LIST This includes enum values. --- CHANGELOG.md | 6 ++++ src/Internal/GenericExporter.php | 28 ++++++++++++++++- src/VarExporter.php | 9 +++++- tests/VarExporterTest.php | 52 ++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 762b4fb..9d13a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## UNRELEASED (0.7.0) + +✨ **New features** + +- Added `VarExporter::INLINE_LITERAL_LIST`, similar to `INLINE_SCALAR_LIST` but includes enum values + ## [0.6.0](https://github.com/brick/varexporter/releases/tag/0.6.0) - 2025-02-20 💥 **BC breaks** diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 3f57ec0..61e8987 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -7,6 +7,7 @@ use Brick\VarExporter\ExportException; use Brick\VarExporter\VarExporter; use ReflectionObject; +use UnitEnum; use function array_is_list; use function array_merge; @@ -52,6 +53,11 @@ final class GenericExporter */ public bool $inlineScalarList; + /** + * @psalm-readonly + */ + public bool $inlineLiteralList; + /** * @psalm-readonly */ @@ -111,6 +117,7 @@ public function __construct(int $options, int $indentLevel = 0) $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); $this->inlineArray = (bool) ($options & VarExporter::INLINE_ARRAY); $this->inlineScalarList = (bool) ($options & VarExporter::INLINE_SCALAR_LIST); + $this->inlineLiteralList = (bool) ($options & VarExporter::INLINE_LITERAL_LIST); $this->closureSnapshotUses = (bool) ($options & VarExporter::CLOSURE_SNAPSHOT_USES); $this->trailingCommaInArray = (bool) ($options & VarExporter::TRAILING_COMMA_IN_ARRAY); @@ -173,7 +180,9 @@ public function exportArray(array $array, array $path, array $parentIds): array $current = 0; - $inline = $this->inlineArray || ($this->inlineScalarList && $isList && $this->isScalarList($array)); + $inline = $this->inlineArray + || ($this->inlineScalarList && $isList && $this->isScalarList($array)) + || ($this->inlineLiteralList && $isList && $this->isLiteralList($array)); foreach ($array as $key => $value) { $isLast = (++$current === $count); @@ -304,4 +313,21 @@ private function isScalarList(array $array): bool return true; } + + /** + * Returns whether the given array only contains literal values. + * + * Values considered literal are: int, float, string, bool, null, and enum values. + * If the array is empty, this method returns true. + */ + private function isLiteralList(array $array): bool + { + foreach ($array as $value) { + if ($value !== null && ! is_scalar($value) && ! $value instanceof UnitEnum) { + return false; + } + } + + return true; + } } diff --git a/src/VarExporter.php b/src/VarExporter.php index fe62c8c..687a1ca 100644 --- a/src/VarExporter.php +++ b/src/VarExporter.php @@ -58,7 +58,7 @@ final class VarExporter /** * Formats lists (0-based numeric arrays) containing only scalar values on a single line. * Types considered scalar here are int, bool, float, string and null. - * This option is a subset of INLINE_ARRAY, and has no effect when INLINE_ARRAY is used. + * This option is a subset of INLINE_ARRAY and INLINE_LITERAL_LIST and has no effect when either is used. */ public const INLINE_SCALAR_LIST = 1 << 7; @@ -82,6 +82,13 @@ final class VarExporter */ public const INLINE_ARRAY = 1 << 11; + /** + * Formats lists (0-based numeric arrays) containing only literal values on a single line. + * Values considered literal are: int, float, string, bool, null, and enum values. + * This option is a subset of INLINE_ARRAY and has no effect when INLINE_ARRAY is used. + */ + public const INLINE_LITERAL_LIST = 1 << 12; + /** * @param mixed $var The variable to export. * @param int $options A bitmask of options. Possible values are `VarExporter::*` constants. diff --git a/tests/VarExporterTest.php b/tests/VarExporterTest.php index 61f2ac5..7898745 100644 --- a/tests/VarExporterTest.php +++ b/tests/VarExporterTest.php @@ -6,6 +6,7 @@ use ArrayIterator; use Brick\VarExporter\ExportException; +use Brick\VarExporter\Tests\Classes\Enum; use Brick\VarExporter\Tests\Classes\PublicPropertiesOnly; use Brick\VarExporter\Tests\Classes\SetState; use Brick\VarExporter\VarExporter; @@ -176,6 +177,57 @@ public function testInlineScalarList(): void $this->assertExportEquals($expected, $var, VarExporter::INLINE_SCALAR_LIST); } + public function testInlineScalarListWithEnum(): void + { + $var = [ + 'one' => ['hello', 'world', 123, true, false, null, 7.5, Enum::TEST], + 'two' => ['hello', 'world', ['one', 'two', 'three']], + ]; + + $expected = <<<'PHP' + [ + 'one' => [ + 'hello', + 'world', + 123, + true, + false, + null, + 7.5, + Brick\VarExporter\Tests\Classes\Enum::TEST + ], + 'two' => [ + 'hello', + 'world', + ['one', 'two', 'three'] + ] + ] + PHP; + + $this->assertExportEquals($expected, $var, VarExporter::INLINE_SCALAR_LIST); + } + + public function testInlineLiteralList(): void + { + $var = [ + 'one' => ['hello', 'world', 123, true, false, null, 7.5, Enum::TEST], + 'two' => ['hello', 'world', ['one', 'two', 'three']], + ]; + + $expected = <<<'PHP' + [ + 'one' => ['hello', 'world', 123, true, false, null, 7.5, Brick\VarExporter\Tests\Classes\Enum::TEST], + 'two' => [ + 'hello', + 'world', + ['one', 'two', 'three'] + ] + ] + PHP; + + $this->assertExportEquals($expected, $var, VarExporter::INLINE_LITERAL_LIST); + } + public function testTrailingCommaInArray(): void { $var = [ From eeb021af4df7f715c92ed067162ca86073a47774 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 19:35:08 +0100 Subject: [PATCH 122/129] Clean up annotations for PHP < 8.1 --- tests/ExportObjectTest.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/ExportObjectTest.php b/tests/ExportObjectTest.php index 961638e..d1530af 100644 --- a/tests/ExportObjectTest.php +++ b/tests/ExportObjectTest.php @@ -291,9 +291,6 @@ public function testExportClassWithPublicPropertiesAndConstructor(): void $this->assertExportEquals($expected, $object); } - /** - * @requires PHP 8.1 - */ public function testExportClassWithReadonlyPublicPropertiesAndConstructor(): void { $object = new ReadonlyPropertiesWithConstructor('public readonly', 'private readonly', 'public'); @@ -317,9 +314,6 @@ public function testExportClassWithReadonlyPublicPropertiesAndConstructor(): voi $this->assertExportEquals($expected, $object); } - /** - * @requires PHP 8.1 - */ public function testExportClassWithStateAndReadonlyPublicProperties(): void { $object = new PublicReadonlyPropertiesWithoutConstructor(); @@ -563,9 +557,6 @@ public function testExportObjectWithRestrictiveOptions(): void $this->assertExportThrows($expectedMessage, $object, VarExporter::NOT_ANY_OBJECT); } - /** - * @requires PHP 8.1 - */ public function testExportEnum(): void { $object = Enum::TEST; From 52d7242a780491831a21fdd3f1d8bed5367a2e12 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 19:35:20 +0100 Subject: [PATCH 123/129] Require PHP 8.2 --- .github/workflows/ci.yml | 3 +-- CHANGELOG.md | 4 ++++ README.md | 4 ++-- composer.json | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e51b4dc..bc2cf23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,6 @@ jobs: fail-fast: false matrix: php-version: - - "8.1" - "8.2" - "8.3" - "8.4" @@ -50,7 +49,7 @@ jobs: deps: - "highest" include: - - php-version: "8.1" + - php-version: "8.2" deps: "lowest" steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d13a3b..432df30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Added `VarExporter::INLINE_LITERAL_LIST`, similar to `INLINE_SCALAR_LIST` but includes enum values +💥 **BC breaks** + +- Minimum PHP version is now `8.2` + ## [0.6.0](https://github.com/brick/varexporter/releases/tag/0.6.0) - 2025-02-20 💥 **BC breaks** diff --git a/README.md b/README.md index 5ac3b4f..fecbfc0 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,9 @@ composer require brick/varexporter ### Requirements -This library requires PHP 8.1 or later. +This library requires PHP 8.2 or later. -For PHP 7.4 and PHP 8.0 compatibility, you can use version `0.5`. For PHP 7.2 & 7.3, you can use version `0.3`. Note that [these PHP versions are EOL](http://php.net/supported-versions.php) and not supported anymore. If you're still using one of these PHP versions, you should consider upgrading as soon as possible. +For PHP 8.1 compatibility, you can use version `0.6`. For PHP 7.4 and PHP 8.0, you can use version `0.5`. For PHP 7.2 & 7.3, you can use version `0.3`. Note that [these PHP versions are EOL](http://php.net/supported-versions.php) and not supported anymore. If you're still using one of these PHP versions, you should consider upgrading as soon as possible. ### Project status & release process diff --git a/composer.json b/composer.json index fe402a9..46cdb42 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "license": "MIT", "require": { - "php": "^8.1", + "php": "^8.2", "nikic/php-parser": "^5.0" }, "require-dev": { From 35da8ce8843006f9ac95ff86c3da28a4d22cb93e Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 23:29:29 +0100 Subject: [PATCH 124/129] Update brick/coding-standard to v4 --- .github/workflows/ci.yml | 2 +- tools/ecs/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc2cf23..61d535d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: coding-standard: name: Coding Standard - uses: brick/coding-standard/.github/workflows/coding-standard.yml@v2 + uses: brick/coding-standard/.github/workflows/coding-standard.yml@v4 with: working-directory: "tools/ecs" diff --git a/tools/ecs/composer.json b/tools/ecs/composer.json index e70d633..5298cff 100644 --- a/tools/ecs/composer.json +++ b/tools/ecs/composer.json @@ -1,6 +1,6 @@ { "require": { - "brick/coding-standard": "v2" + "brick/coding-standard": "v4" }, "config": { "allow-plugins": { From 73425309f40e87775ef688089d39853828e9524f Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 23:31:07 +0100 Subject: [PATCH 125/129] Update to PHPUnit 11 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 46cdb42..682066f 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "nikic/php-parser": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.5", + "phpunit/phpunit": "^11.0", "php-coveralls/php-coveralls": "^2.2", "vimeo/psalm": "6.14.3" }, From 7998fbb1473c6aaaf062f772fe84205768caa89d Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 23:39:57 +0100 Subject: [PATCH 126/129] Readonly properties --- src/Internal/GenericExporter.php | 60 +++++++++++--------------------- src/Internal/ObjectExporter.php | 2 +- 2 files changed, 21 insertions(+), 41 deletions(-) diff --git a/src/Internal/GenericExporter.php b/src/Internal/GenericExporter.php index 61e8987..5f677ed 100644 --- a/src/Internal/GenericExporter.php +++ b/src/Internal/GenericExporter.php @@ -33,50 +33,26 @@ */ final class GenericExporter { - /** - * @psalm-readonly - */ - public bool $addTypeHints; + public readonly bool $addTypeHints; - /** - * @psalm-readonly - */ - public bool $skipDynamicProperties; + public readonly bool $skipDynamicProperties; - /** - * @psalm-readonly - */ - public bool $inlineArray; + public readonly bool $inlineArray; - /** - * @psalm-readonly - */ - public bool $inlineScalarList; + public readonly bool $inlineScalarList; - /** - * @psalm-readonly - */ - public bool $inlineLiteralList; + public readonly bool $inlineLiteralList; - /** - * @psalm-readonly - */ - public bool $closureSnapshotUses; + public readonly bool $closureSnapshotUses; - /** - * @psalm-readonly - */ - public bool $trailingCommaInArray; + public readonly bool $trailingCommaInArray; - /** - * @psalm-readonly - */ - public int $indentLevel; + public readonly int $indentLevel; /** * @var ObjectExporter[] */ - private array $objectExporters = []; + private readonly array $objectExporters; /** * The visited objects, to detect circular references. @@ -89,30 +65,34 @@ final class GenericExporter public function __construct(int $options, int $indentLevel = 0) { - $this->objectExporters[] = new ObjectExporter\StdClassExporter($this); + $objectExporters = [ + new ObjectExporter\StdClassExporter($this), + ]; if (($options & VarExporter::NO_CLOSURES) === 0) { - $this->objectExporters[] = new ObjectExporter\ClosureExporter($this); + $objectExporters[] = new ObjectExporter\ClosureExporter($this); } if (($options & VarExporter::NO_SET_STATE) === 0) { - $this->objectExporters[] = new ObjectExporter\SetStateExporter($this); + $objectExporters[] = new ObjectExporter\SetStateExporter($this); } - $this->objectExporters[] = new ObjectExporter\InternalClassExporter($this); + $objectExporters[] = new ObjectExporter\InternalClassExporter($this); if (($options & VarExporter::NO_SERIALIZE) === 0) { - $this->objectExporters[] = new ObjectExporter\SerializeExporter($this); + $objectExporters[] = new ObjectExporter\SerializeExporter($this); } if (($options & VarExporter::NO_ENUMS) === 0) { - $this->objectExporters[] = new ObjectExporter\EnumExporter($this); + $objectExporters[] = new ObjectExporter\EnumExporter($this); } if (($options & VarExporter::NOT_ANY_OBJECT) === 0) { - $this->objectExporters[] = new ObjectExporter\AnyObjectExporter($this); + $objectExporters[] = new ObjectExporter\AnyObjectExporter($this); } + $this->objectExporters = $objectExporters; + $this->addTypeHints = (bool) ($options & VarExporter::ADD_TYPE_HINTS); $this->skipDynamicProperties = (bool) ($options & VarExporter::SKIP_DYNAMIC_PROPERTIES); $this->inlineArray = (bool) ($options & VarExporter::INLINE_ARRAY); diff --git a/src/Internal/ObjectExporter.php b/src/Internal/ObjectExporter.php index 9497a3c..3f318a1 100644 --- a/src/Internal/ObjectExporter.php +++ b/src/Internal/ObjectExporter.php @@ -17,7 +17,7 @@ */ abstract class ObjectExporter { - protected GenericExporter $exporter; + protected readonly GenericExporter $exporter; public function __construct(GenericExporter $exporter) { From 83fa26f6fb4f107286499c35a5ec07b9d98efd5b Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 23:44:58 +0100 Subject: [PATCH 127/129] Promoted property --- src/Internal/ObjectExporter.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Internal/ObjectExporter.php b/src/Internal/ObjectExporter.php index 3f318a1..02fb7c3 100644 --- a/src/Internal/ObjectExporter.php +++ b/src/Internal/ObjectExporter.php @@ -17,11 +17,9 @@ */ abstract class ObjectExporter { - protected readonly GenericExporter $exporter; - - public function __construct(GenericExporter $exporter) - { - $this->exporter = $exporter; + public function __construct( + protected readonly GenericExporter $exporter, + ) { } /** From b3a50b8f630a9ed5015ea3e1f00479af261ed80d Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Tue, 6 Jan 2026 23:56:00 +0100 Subject: [PATCH 128/129] Prepare for release --- CHANGELOG.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 432df30..923e9c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## UNRELEASED (0.7.0) +## [0.7.0](https://github.com/brick/varexporter/releases/tag/0.7.0) - 2026-01-06 ✨ **New features** diff --git a/README.md b/README.md index fecbfc0..c110ce4 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The current releases are numbered `0.x.y`. When a non-breaking change is introdu **When a breaking change is introduced, a new `0.x` version cycle is always started.** -It is therefore safe to lock your project to a given release cycle, such as `0.6.*`. +It is therefore safe to lock your project to a given release cycle, such as `0.7.*`. If you need to upgrade to a newer release cycle, check the [release history](https://github.com/brick/varexporter/releases) for a list of changes introduced by each further `0.x.0` version. From ead7271712af1e49f6c97049c6c65cf7523b62d0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 00:46:05 +0100 Subject: [PATCH 129/129] Migrate coverage reporting from Coveralls to Codecov (#40) --- .github/workflows/ci.yml | 10 ++++------ README.md | 2 +- composer.json | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61d535d..323ea04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,13 +72,11 @@ jobs: if: ${{ matrix.php-version != env.COVERAGE_PHP_VERSION }} - name: Run PHPUnit with coverage - run: | - mkdir -p build/logs - vendor/bin/phpunit --coverage-clover build/logs/clover.xml + run: vendor/bin/phpunit --coverage-clover clover.xml if: ${{ matrix.php-version == env.COVERAGE_PHP_VERSION }} - - name: Upload coverage report to Coveralls - run: vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} if: ${{ matrix.php-version == env.COVERAGE_PHP_VERSION }} diff --git a/README.md b/README.md index c110ce4..0571437 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A powerful and pretty replacement for PHP's `var_export()`. [![Build Status](https://github.com/brick/varexporter/workflows/CI/badge.svg)](https://github.com/brick/varexporter/actions) -[![Coverage Status](https://coveralls.io/repos/github/brick/varexporter/badge.svg?branch=master)](https://coveralls.io/github/brick/varexporter?branch=master) +[![Coverage Status](https://codecov.io/github/brick/varexporter/graph/badge.svg)](https://codecov.io/github/brick/varexporter) [![Latest Stable Version](https://poser.pugx.org/brick/varexporter/v/stable)](https://packagist.org/packages/brick/varexporter) [![Total Downloads](https://poser.pugx.org/brick/varexporter/downloads)](https://packagist.org/packages/brick/varexporter) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) diff --git a/composer.json b/composer.json index 682066f..233e1da 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,6 @@ }, "require-dev": { "phpunit/phpunit": "^11.0", - "php-coveralls/php-coveralls": "^2.2", "vimeo/psalm": "6.14.3" }, "autoload": {