From 5553558bd381fce5108c6d0343c12e488cfec6bb Mon Sep 17 00:00:00 2001 From: smiley Date: Wed, 17 Jul 2024 03:04:28 +0200 Subject: [PATCH] :octocat: backport changes from v3.2.1 (https://github.com/chillerlan/php-settings-container/releases/tag/3.2.1) --- .gitattributes | 33 +++++++------ .github/workflows/tests.yml | 38 ++++++++------ .gitignore | 1 + .phan/config.php | 55 --------------------- README.md | 79 +++++++++++++++++------------- composer.json | 13 ++--- examples/advanced.php | 32 +++++++++--- examples/simple.php | 10 ++-- phpstan-baseline.neon | 36 ++++++++++++++ phpstan.dist.neon | 16 ++++++ phpunit.xml.dist | 9 ++-- rules-magic-access.neon | 4 ++ src/SettingsContainerAbstract.php | 31 +++++++++--- src/SettingsContainerInterface.php | 19 ++++++- tests/ContainerTest.php | 35 ++++++++----- tests/TestContainer.php | 9 +--- tests/TestOptionsTrait.php | 13 ++++- 17 files changed, 260 insertions(+), 173 deletions(-) delete mode 100644 .phan/config.php create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.dist.neon create mode 100644 rules-magic-access.neon diff --git a/.gitattributes b/.gitattributes index 8add556..aec5c58 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,17 +1,20 @@ -/.build export-ignore -/.github export-ignore -/.phan export-ignore -/.phpdoc export-ignore -/docs export-ignore -/examples export-ignore -/tests export-ignore -/.editorconfig export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore -/.readthedocs.yml export-ignore -/phpcs.xml.dist export-ignore -/phpdoc.xml.dist export-ignore -/phpmd.xml.dist export-ignore -/phpunit.xml.dist export-ignore +/.build export-ignore +/.github export-ignore +/.idea export-ignore +/.phan export-ignore +/.phpdoc export-ignore +/docs export-ignore +/examples export-ignore +/tests export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.readthedocs.yml export-ignore +/phpcs.xml.dist export-ignore +/phpdoc.xml.dist export-ignore +/phpmd.xml.dist export-ignore +/phpunit.xml.dist export-ignore +/phpstan.dist.neon export-ignore +/phpstan-baseline.neon export-ignore *.php diff=php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f6dfba1..01ffa90 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,8 @@ # https://help.github.com/en/categories/automating-your-workflow-with-github-actions # https://github.com/sebastianbergmann/phpunit/blob/master/.github/workflows/ci.yml +name: "CI" + on: push: branches: @@ -10,19 +12,17 @@ on: - v2.x-php7.4 -name: "CI" +env: + PHP_EXTENSIONS: json + PHP_INI_VALUES: memory_limit=-1, error_reporting=-1, display_errors=On + jobs: static-code-analysis: name: "Static Code Analysis" - runs-on: ubuntu-latest - env: - PHAN_ALLOW_XDEBUG: 0 - PHAN_DISABLE_XDEBUG_WARN: 1 - strategy: fail-fast: true matrix: @@ -32,24 +32,25 @@ jobs: - "8.1" - "8.2" - "8.3" +# - "8.4" steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install PHP" uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} - tools: pecl + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }} coverage: none - extensions: ast, json - name: "Update dependencies with composer" - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 - - name: "Run phan" - run: php vendor/bin/phan --target-php-version=${{ matrix.php-version }} + - name: "Run PHPStan" + run: php vendor/bin/phpstan tests: @@ -69,26 +70,31 @@ jobs: - "8.1" - "8.2" - "8.3" +# - "8.4" steps: - name: "Checkout" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install PHP with extensions" uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }} coverage: pcov - extensions: json - name: "Install dependencies with composer" - uses: ramsey/composer-install@v2 + uses: ramsey/composer-install@v3 - name: "Run tests with phpunit" run: php vendor/phpunit/phpunit/phpunit --configuration=phpunit.xml.dist - name: "Send code coverage report to Codecov.io" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: .build/coverage/clover.xml - name: "Send code coverage report to Codacy" uses: codacy/codacy-coverage-reporter-action@v1 diff --git a/.gitignore b/.gitignore index d8933ce..e7b9013 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ phpcs.xml phpdoc.xml phpmd.xml phpunit.xml +phpstan.neon diff --git a/.phan/config.php b/.phan/config.php deleted file mode 100644 index 4221756..0000000 --- a/.phan/config.php +++ /dev/null @@ -1,55 +0,0 @@ - null, - 'minimum_target_php_version' => '7.4', - - // A list of directories that should be parsed for class and - // method information. After excluding the directories - // defined in exclude_analysis_directory_list, the remaining - // files will be statically analyzed for errors. - // - // Thus, both first-party and third-party code being used by - // your application should be included in this list. - 'directory_list' => [ - 'examples', - 'src', - 'tests', - 'vendor', - ], - - // A regex used to match every file name that you want to - // exclude from parsing. Actual value will exclude every - // "test", "tests", "Test" and "Tests" folders found in - // "vendor/" directory. - 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@', - - // A directory list that defines files that will be excluded - // from static analysis, but whose class and method - // information should be included. - // - // Generally, you'll want to include the directories for - // third-party code (such as "vendor/") in this list. - // - // n.b.: If you'd like to parse but not analyze 3rd - // party code, directories containing that code - // should be added to both the `directory_list` - // and `exclude_analysis_directory_list` arrays. - 'exclude_analysis_directory_list' => [ - 'tests', - 'vendor', - ], -]; diff --git a/README.md b/README.md index a21653a..68d077a 100644 --- a/README.md +++ b/README.md @@ -45,18 +45,14 @@ Profit! ## Usage -The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract` ) provides plug-in functionality for immutable object properties and adds some fancy, like loading/saving JSON, arrays etc. +The `SettingsContainerInterface` (wrapped in`SettingsContainerAbstract`) provides plug-in functionality for immutable object properties and adds some fancy, like loading/saving JSON, arrays etc. It takes an `iterable` as the only constructor argument and calls a method with the trait's name on invocation (`MyTrait::MyTrait()`) for each used trait. +A PHPStan ruleset to exclude errors generated by accessing magic properties on `SettingsContainerInterface` can be found in `rules-magic-access.neon`. + + ### Simple usage ```php -class MyContainer extends SettingsContainerAbstract{ - protected $foo; - protected $bar; -} -``` -Typed properties in PHP 7.4+: -```php class MyContainer extends SettingsContainerAbstract{ protected string $foo; protected string $bar; @@ -64,12 +60,12 @@ class MyContainer extends SettingsContainerAbstract{ ``` ```php -// use it just like a \stdClass +// use it just like a \stdClass (except the properties are fixed) $container = new MyContainer; $container->foo = 'what'; $container->bar = 'foo'; -// which is equivalent to +// which is equivalent to $container = new MyContainer(['bar' => 'foo', 'foo' => 'what']); // ...or try $container->fromJSON('{"foo": "what", "bar": "foo"}'); @@ -90,37 +86,48 @@ var_dump($container->nope); // -> null ### Advanced usage ```php +// from library 1 trait SomeOptions{ - protected $foo; - protected $what; - + protected string $foo; + protected string $what; + // this method will be called in SettingsContainerAbstract::construct() // after the properties have been set - protected function SomeOptions(){ + protected function SomeOptions():void{ // just some constructor stuff... $this->foo = strtoupper($this->foo); } - + + /* + * special prefixed magic setters & getters + */ + // this method will be called from __set() when property $what is set - protected function set_what(string $value){ + protected function set_what(string $value):void{ $this->what = md5($value); } + + // this method is called on __get() for the property $what + protected function get_what():string{ + return 'hash: '.$this->what; + } } +// from library 2 trait MoreOptions{ - protected $bar = 'whatever'; // provide default values + protected string $bar = 'whatever'; // provide default values } ``` ```php $commonOptions = [ // SomeOptions - 'foo' => 'whatever', + 'foo' => 'whatever', // MoreOptions 'bar' => 'nothing', ]; -// now plug the several library options together to a single object +// now plug the several library options together to a single object $container = new class ($commonOptions) extends SettingsContainerAbstract{ use SomeOptions, MoreOptions; }; @@ -129,27 +136,31 @@ var_dump($container->foo); // -> WHATEVER (constructor ran strtoupper on the val var_dump($container->bar); // -> nothing $container->what = 'some value'; -var_dump($container->what); // -> md5 hash of "some value" +var_dump($container->what); // -> hash: 5946210c9e93ae37891dfe96c3e39614 (custom getter added "hash: ") ``` ### API #### [`SettingsContainerAbstract`](https://github.com/chillerlan/php-settings-container/blob/main/src/SettingsContainerAbstract.php) -method | return | info --------- | ---- | ----------- -`__construct(iterable $properties = null)` | - | calls `construct()` internally after the properties have been set -(protected) `construct()` | void | calls a method with trait name as replacement constructor for each used trait -`__get(string $property)` | mixed | calls `$this->{'get_'.$property}()` if such a method exists -`__set(string $property, $value)` | void | calls `$this->{'set_'.$property}($value)` if such a method exists -`__isset(string $property)` | bool | -`__unset(string $property)` | void | -`__toString()` | string | a JSON string -`toArray()` | array | -`fromIterable(iterable $properties)` | `SettingsContainerInterface` | -`toJSON(int $jsonOptions = null)` | string | accepts [JSON options constants](http://php.net/manual/json.constants.php) -`fromJSON(string $json)` | `SettingsContainerInterface` | -`jsonSerialize()` | mixed | implements the [`JsonSerializable`](https://www.php.net/manual/en/jsonserializable.jsonserialize.php) interface +| method | return | info | +|--------------------------------------------|------------------------------|---------------------------------------------------------------------------------------------------------------------| +| `__construct(iterable $properties = null)` | - | calls `construct()` internally after the properties have been set | +| (protected) `construct()` | void | calls a method with trait name as replacement constructor for each used trait | +| `__get(string $property)` | mixed | calls `$this->{'get_'.$property}()` if such a method exists | +| `__set(string $property, $value)` | void | calls `$this->{'set_'.$property}($value)` if such a method exists | +| `__isset(string $property)` | bool | | +| `__unset(string $property)` | void | | +| `__toString()` | string | a JSON string | +| `toArray()` | array | | +| `fromIterable(iterable $properties)` | `SettingsContainerInterface` | | +| `toJSON(int $jsonOptions = null)` | string | accepts [JSON options constants](http://php.net/manual/json.constants.php) | +| `fromJSON(string $json)` | `SettingsContainerInterface` | | +| `jsonSerialize()` | mixed | implements the [`JsonSerializable`](https://www.php.net/manual/en/jsonserializable.jsonserialize.php) interface | +| `serialize()` | string | implements the [`Serializable`](https://www.php.net/manual/en/serializable.serialize.php) interface | +| `unserialize(string $data)` | void | implements the [`Serializable`](https://www.php.net/manual/en/serializable.unserialize.php) interface | +| `__serialize()` | array | implements the [`Serializable`](https://www.php.net/manual/en/language.oop5.magic.php#object.serialize) interface | +| `__unserialize(array $data)` | void | implements the [`Serializable`](https://www.php.net/manual/en/language.oop5.magic.php#object.unserialize) interface | ## Disclaimer This might be either an utterly genius or completely stupid idea - you decide. However, i like it and it works. diff --git a/composer.json b/composer.json index 573b54c..ac33930 100644 --- a/composer.json +++ b/composer.json @@ -24,24 +24,25 @@ "ext-json": "*" }, "require-dev": { - "phan/phan": "^5.4", - "phpmd/phpmd": "^2.13", + "phpmd/phpmd": "^2.15", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-deprecation-rules": "^1.2", "phpunit/phpunit": "^9.6", - "squizlabs/php_codesniffer": "^3.8" + "squizlabs/php_codesniffer": "^3.10" }, "autoload": { "psr-4": { - "chillerlan\\Settings\\": "src/" + "chillerlan\\Settings\\": "src" } }, "autoload-dev": { "psr-4": { - "chillerlan\\SettingsTest\\": "tests/" + "chillerlan\\SettingsTest\\": "tests" } }, "scripts": { "phpunit": "@php vendor/bin/phpunit", - "phan": "@php vendor/bin/phan" + "phpstan": "@php vendor/bin/phpstan" }, "config": { "lock": false, diff --git a/examples/advanced.php b/examples/advanced.php index 54c45ae..cc128c1 100644 --- a/examples/advanced.php +++ b/examples/advanced.php @@ -10,18 +10,34 @@ require_once __DIR__.'/../vendor/autoload.php'; -// from library #1 +// from library 1 trait SomeOptions{ - protected string $foo = ''; + protected string $foo; + protected string $what; - // this method will be called in SettingsContainerAbstract::__construct() after the properties have been set - protected function SomeOptions(){ + // this method will be called in SettingsContainerAbstract::construct() + // after the properties have been set + protected function SomeOptions():void{ // just some constructor stuff... $this->foo = strtoupper($this->foo); } + + /* + * special prefixed magic setters & getters + */ + + // this method will be called from __set() when property $what is set + protected function set_what(string $value):void{ + $this->what = md5($value); + } + + // this method is called on __get() for the property $what + protected function get_what():string{ + return 'hash: '.$this->what; + } } -// from library #2 +// from library 2 trait MoreOptions{ protected string $bar = 'whatever'; // provide default values } @@ -37,13 +53,17 @@ trait MoreOptions{ /** * @property string $foo + * @property string $what * @property string $bar */ class MySettings extends SettingsContainerAbstract{ use SomeOptions, MoreOptions; // ... }; -$container = new MySettings($commonOptions); +$container = new MySettings($commonOptions); // wtf phpstorm??? var_dump($container->foo); // -> WHATEVER (constructor ran strtoupper on the value) var_dump($container->bar); // -> nothing + +$container->what = 'some value'; +var_dump($container->what); // -> hash: 5946210c9e93ae37891dfe96c3e39614 (custom getter added "hash: ") diff --git a/examples/simple.php b/examples/simple.php index b10bbfd..1c89709 100644 --- a/examples/simple.php +++ b/examples/simple.php @@ -10,18 +10,22 @@ require_once __DIR__.'/../vendor/autoload.php'; +/** + * @property string $foo + * @property string $bar + */ class MyContainer extends SettingsContainerAbstract{ - protected $foo; - protected $bar; + protected string $foo; + protected string $bar; } -/** @var \chillerlan\Settings\SettingsContainerInterface $container */ $container = new MyContainer(['foo' => 'what']); $container->bar = 'foo'; var_dump($container->toJSON()); // -> {"foo":"what","bar":"foo"} // non-existing properties will be ignored: +/** @phpstan-ignore-next-line */ $container->nope = 'what'; var_dump($container->nope); // -> NULL diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..9b45200 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,36 @@ +parameters: + ignoreErrors: + - + message: "#^Access to an undefined property chillerlan\\\\SettingsTest\\\\TestContainer\\:\\:\\$foo\\.$#" + count: 1 + path: tests/ContainerTest.php + + - + message: "#^Access to an undefined property chillerlan\\\\Settings\\\\SettingsContainerInterface\\:\\:\\$test5\\.$#" + count: 1 + path: tests/ContainerTest.php + + - + message: "#^Access to private property chillerlan\\\\SettingsTest\\\\TestContainer\\:\\:\\$test3\\.$#" + count: 4 + path: tests/ContainerTest.php + + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertFalse\\(\\) with true will always evaluate to false\\.$#" + count: 1 + path: tests/ContainerTest.php + + - + message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertNull\\(\\) with string will always evaluate to false\\.$#" + count: 2 + path: tests/ContainerTest.php + + - + message: "#^Property chillerlan\\\\SettingsTest\\\\TestContainer\\:\\:\\$test3 \\(string\\) in isset\\(\\) is not nullable\\.$#" + count: 1 + path: tests/ContainerTest.php + + - + message: "#^Property chillerlan\\\\SettingsTest\\\\TestContainer\\:\\:\\$test3 is never read, only written\\.$#" + count: 1 + path: tests/TestContainer.php diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 0000000..5e0a18d --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,16 @@ +# https://phpstan.org/config-reference + +parameters: + level: 9 + tmpDir: .build/phpstan-cache + paths: + - examples + - src + - tests + + treatPhpDocTypesAsCertain: false + +includes: + - phpstan-baseline.neon + - vendor/phpstan/phpstan/conf/bleedingEdge.neon + - vendor/phpstan/phpstan-deprecation-rules/rules.neon diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9271a1b..baf526f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,6 @@ - ./src + src @@ -17,10 +17,7 @@ - ./tests/ + tests/ - - - diff --git a/rules-magic-access.neon b/rules-magic-access.neon new file mode 100644 index 0000000..5a98d3a --- /dev/null +++ b/rules-magic-access.neon @@ -0,0 +1,4 @@ +parameters: + ignoreErrors: + # yes, these are magic + - message: "#^Access to an undefined property chillerlan\\\\Settings\\\\SettingsContainerInterface\\:\\:\\$[\\w]+\\.$#" diff --git a/src/SettingsContainerAbstract.php b/src/SettingsContainerAbstract.php index fa97db2..081794e 100644 --- a/src/SettingsContainerAbstract.php +++ b/src/SettingsContainerAbstract.php @@ -7,20 +7,22 @@ * @copyright 2018 Smiley * @license MIT */ +declare(strict_types=1); namespace chillerlan\Settings; -use ReflectionClass, ReflectionProperty; - -use function get_object_vars, json_decode, json_encode, method_exists, property_exists; +use JsonException, ReflectionClass, ReflectionProperty; +use function array_keys, get_object_vars, json_decode, json_encode, json_last_error_msg, method_exists, property_exists; use const JSON_THROW_ON_ERROR; abstract class SettingsContainerAbstract implements SettingsContainerInterface{ /** * SettingsContainerAbstract constructor. + * + * @phpstan-param array $properties */ - public function __construct(iterable $properties = null){ + public function __construct(?iterable $properties = null){ if(!empty($properties)){ $this->fromIterable($properties); @@ -120,7 +122,13 @@ public function __toString():string{ * @inheritdoc */ public function toArray():array{ - return get_object_vars($this); + $properties = []; + + foreach(array_keys(get_object_vars($this)) as $key){ + $properties[$key] = $this->__get($key); + } + + return $properties; } /** @@ -138,14 +146,21 @@ public function fromIterable(iterable $properties):SettingsContainerInterface{ /** * @inheritdoc */ - public function toJSON(int $jsonOptions = null):string{ - return json_encode($this, ($jsonOptions ?? 0)); + public function toJSON(?int $jsonOptions = null):string{ + $json = json_encode($this, ($jsonOptions ?? 0)); + + if($json === false){ + throw new JsonException(json_last_error_msg()); + } + + return $json; } /** * @inheritdoc */ public function fromJSON(string $json):SettingsContainerInterface{ + /** @phpstan-var array $data */ $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); return $this->fromIterable($data); @@ -153,7 +168,7 @@ public function fromJSON(string $json):SettingsContainerInterface{ /** * @inheritdoc - * @phan-suppress PhanUndeclaredClassAttribute + * @return array */ #[\ReturnTypeWillChange] public function jsonSerialize():array{ diff --git a/src/SettingsContainerInterface.php b/src/SettingsContainerInterface.php index ddacccd..5ac2d29 100644 --- a/src/SettingsContainerInterface.php +++ b/src/SettingsContainerInterface.php @@ -7,6 +7,7 @@ * @copyright 2018 Smiley * @license MIT */ +declare(strict_types=1); namespace chillerlan\Settings; @@ -43,29 +44,43 @@ public function __isset(string $property):bool; public function __unset(string $property):void; /** - * @see SettingsContainerInterface::toJSON() + * @see \chillerlan\Settings\SettingsContainerInterface::toJSON() */ public function __toString():string; /** * Returns an array representation of the settings object + * + * The values will be run through the magic __get(), which may also call custom getters. + * + * @return array */ public function toArray():array; /** * Sets properties from a given iterable + * + * The values will be run through the magic __set(), which may also call custom setters. + * + * @phpstan-param array $properties */ public function fromIterable(iterable $properties):SettingsContainerInterface; /** * Returns a JSON representation of the settings object + * * @see \json_encode() + * @see \chillerlan\Settings\SettingsContainerInterface::toArray() + * + * @throws \JsonException */ - public function toJSON(int $jsonOptions = null):string; + public function toJSON(?int $jsonOptions = null):string; /** * Sets properties from a given JSON string * + * @see \chillerlan\Settings\SettingsContainerInterface::fromIterable() + * * @throws \Exception * @throws \JsonException */ diff --git a/tests/ContainerTest.php b/tests/ContainerTest.php index 0e30b0f..0ef9dca 100644 --- a/tests/ContainerTest.php +++ b/tests/ContainerTest.php @@ -7,16 +7,17 @@ * @copyright 2018 Smiley * @license MIT */ +declare(strict_types=1); namespace chillerlan\SettingsTest; use PHPUnit\Framework\TestCase; -use JsonException, TypeError; -use function sha1; +use InvalidArgumentException, JsonException, TypeError; +use function json_encode, serialize, sha1, unserialize; class ContainerTest extends TestCase{ - public function testConstruct(){ + public function testConstruct():void{ $container = new TestContainer([ 'test1' => 'test1', 'test2' => true, @@ -32,7 +33,7 @@ public function testConstruct(){ $this::assertSame('success', $container->testConstruct); } - public function testGet(){ + public function testGet():void{ $container = new TestContainer; $this::assertSame('foo', $container->test1); @@ -57,7 +58,7 @@ public function testGet(){ $this::assertSame('null', $container->test6); } - public function testSet(){ + public function testSet():void{ $container = new TestContainer; $container->test1 = 'bar'; $container->test2 = false; @@ -76,7 +77,7 @@ public function testSet(){ $this::assertSame('bar_test5', $container->test5); } - public function testToArray(){ + public function testToArray():void{ $container = new TestContainer([ 'test1' => 'no', 'test2' => true, @@ -88,26 +89,36 @@ public function testToArray(){ 'test2' => true, 'testConstruct' => 'success', 'test4' => null, - 'test5' => null, - 'test6' => null, + 'test5' => '', + 'test6' => 'null', // value ran through the getter ], $container->toArray()); + + $container->fromIterable($container->toArray()); + + $this::assertSame('_test5', $container->test5); // value ran through the setter } - public function testToJSON(){ + public function testToJSON():void{ $container = (new TestContainer)->fromJSON('{"test1":"no","test2":true,"testConstruct":"success"}'); - $expected = '{"test1":"no","test2":true,"testConstruct":"success","test4":null,"test5":null,"test6":null}'; + $expected = '{"test1":"no","test2":true,"testConstruct":"success","test4":null,"test5":"","test6":"null"}'; $this::assertSame($expected, $container->toJSON()); $this::assertSame($expected, (string)$container); + $this::assertSame($expected, json_encode($container)); // JsonSerializable + + $container->fromJSON($expected); + + $this::assertSame('_test5', $container->test5); } - public function testFromJsonException(){ + public function testFromJsonException():void{ $this->expectException(JsonException::class); (new TestContainer)->fromJSON('-'); } - public function testFromJsonTypeError(){ + + public function testFromJsonTypeError():void{ $this->expectException(TypeError::class); (new TestContainer)->fromJSON('2'); } diff --git a/tests/TestContainer.php b/tests/TestContainer.php index a83f4be..bf3b479 100644 --- a/tests/TestContainer.php +++ b/tests/TestContainer.php @@ -7,19 +7,12 @@ * @copyright 2018 Smiley * @license MIT */ +declare(strict_types=1); namespace chillerlan\SettingsTest; use chillerlan\Settings\SettingsContainerAbstract; -/** - * @property $test1 - * @property $test2 - * @property $test3 - * @property $test4 - * @property $test5 - * @property $test6 - */ class TestContainer extends SettingsContainerAbstract{ use TestOptionsTrait; diff --git a/tests/TestOptionsTrait.php b/tests/TestOptionsTrait.php index c8c628b..862cce8 100644 --- a/tests/TestOptionsTrait.php +++ b/tests/TestOptionsTrait.php @@ -7,11 +7,20 @@ * @copyright 2018 smiley * @license MIT */ +declare(strict_types=1); namespace chillerlan\SettingsTest; use function sha1; +/** + * @property string $test1 + * @property bool|null $test2 + * @property string $testConstruct + * @property string|null $test4 + * @property string $test5 + * @property string|null $test6 + */ trait TestOptionsTrait{ protected string $test1 = 'foo'; @@ -22,7 +31,7 @@ trait TestOptionsTrait{ protected ?string $test4 = null; - protected ?string $test5 = null; + protected string $test5 = ''; protected ?string $test6 = null; @@ -30,7 +39,7 @@ protected function TestOptionsTrait():void{ $this->testConstruct = 'success'; } - protected function set_test5($value):void{ + protected function set_test5(string $value):void{ $this->test5 = $value.'_test5'; }