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';
}