diff --git a/.github/workflows/test-suite.yaml b/.github/workflows/test-suite.yaml
index a3abc65..ca77cc5 100644
--- a/.github/workflows/test-suite.yaml
+++ b/.github/workflows/test-suite.yaml
@@ -28,7 +28,7 @@ jobs:
run: composer update
- name: Run Static Analysis
- run: vendor/bin/psalm
+ run: vendor/bin/phpstan
tests:
name: Tests
diff --git a/Makefile b/Makefile
index 7f91e4f..baeb3c2 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ prod production: # Build application for production
test: # Run coding standards/static analysis checks and tests
@vendor/bin/php-cs-fixer fix --diff --dry-run \
- && vendor/bin/psalm \
+ && vendor/bin/phpstan \
&& vendor/bin/phpunit --coverage-text
coverage: # Generate an HTML coverage report
diff --git a/composer.json b/composer.json
index 394be37..d93c860 100644
--- a/composer.json
+++ b/composer.json
@@ -24,9 +24,9 @@
"yosymfony/toml": "^1.0"
},
"require-dev": {
- "phlak/coding-standards": "^2.0",
+ "phlak/coding-standards": "^2.2",
+ "phpstan/phpstan": "^1.10",
"psy/psysh": "^0.11",
- "vimeo/psalm": "^5.15",
"yoast/phpunit-polyfills": "^2.0"
},
"autoload": {
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 0000000..e69de29
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..f6b0a56
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,27 @@
+parameters:
+
+ paths:
+ - src
+ - tests
+
+ level: max
+
+ checkFunctionNameCase: true
+ checkMissingIterableValueType: false
+
+ reportUnmatchedIgnoredErrors: false
+
+ exceptions:
+ implicitThrows: false
+
+ check:
+ missingCheckedExceptionInThrows: true
+ tooWideThrowType: true
+
+ uncheckedExceptionClasses:
+ - 'InvalidArgumentException'
+ - 'RuntimeException'
+ - 'PHPUnit\Framework\Exception'
+
+includes:
+ - phpstan-baseline.neon
diff --git a/psalm.xml b/psalm.xml
deleted file mode 100644
index 5bfb8e3..0000000
--- a/psalm.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Config.php b/src/Config.php
index 0933772..0d94877 100644
--- a/src/Config.php
+++ b/src/Config.php
@@ -7,10 +7,15 @@
use IteratorAggregate;
use PHLAK\Config\Exceptions\InvalidContextException;
use PHLAK\Config\Interfaces\ConfigInterface;
+use PHLAK\Config\Loaders\Loader;
use PHLAK\Config\Traits\Arrayable;
use RuntimeException;
use SplFileInfo;
+/**
+ * @implements ArrayAccess
+ * @implements IteratorAggregate
+ */
class Config implements ConfigInterface, ArrayAccess, IteratorAggregate
{
use Arrayable;
@@ -21,13 +26,13 @@ class Config implements ConfigInterface, ArrayAccess, IteratorAggregate
/**
* Create a new Config object.
*
- * @param mixed $context Raw array of configuration options or path to a
- * configuration file or directory containing one or
- * more configuration files
- * @param string|array $prefix A key under which the loaded config will be nested
+ * @param array|string $context Raw array of configuration options or path to a configuration file
+ * or directory containing one or more configuration files
+ * @param string $prefix A key under which the loaded config will be nested
+ *
* @throws InvalidContextException
*/
- public function __construct($context = null, string $prefix = null)
+ public function __construct(array|string $context = null, string $prefix = null)
{
switch (gettype($context)) {
case 'NULL':
@@ -50,11 +55,13 @@ public function __construct($context = null, string $prefix = null)
*
* @param string $path A path to a directory of configuration files
*
+ * @throws InvalidContextException
+ *
* @return \PHLAK\Config\Interfaces\ConfigInterface A new ConfigInterface object
*/
public static function fromDirectory(string $path): ConfigInterface
{
- $config = new self();
+ $config = new self;
foreach (new DirectoryIterator($path) as $file) {
if ($file->isFile()) {
@@ -93,7 +100,7 @@ public function set(string $key, mixed $value): bool
* Retrieve a configuration option via a provided key.
*
* @param string $key Unique configuration option key
- * @param mixed|null $default Default value to return if option does not exist
+ * @param mixed $default Default value to return if option does not exist
*
* @return mixed Stored config item or $default value
*/
@@ -138,10 +145,9 @@ public function has(string $key): bool
* @param string $key Unique configuration option key
* @param mixed $value Config item value
*
- * @return true
- *
* @throws RuntimeException
*
+ * @return true
*/
public function append(string $key, mixed $value): bool
{
@@ -166,10 +172,9 @@ public function append(string $key, mixed $value): bool
* @param string $key Unique configuration option key
* @param mixed $value Config item value
*
- * @return true
- *
* @throws RuntimeException
*
+ * @return true
*/
public function prepend(string $key, mixed $value): bool
{
@@ -221,6 +226,7 @@ public function load(string $path, string $prefix = null, bool $override = true)
$className = $file->isDir() ? 'Directory' : ucfirst(strtolower($file->getExtension()));
$classPath = 'PHLAK\\Config\\Loaders\\' . $className;
+ /** @var Loader $loader */
$loader = new $classPath($file->getRealPath());
$newConfig = $prefix ? [$prefix => $loader->getArray()] : $loader->getArray();
@@ -259,8 +265,9 @@ public function merge(ConfigInterface $config, bool $override = true): ConfigInt
*
* @param string $key Unique configuration option key
*
- * @return \PHLAK\Config\Interfaces\ConfigInterface A new ConfigInterface object
* @throws InvalidContextException
+ *
+ * @return ConfigInterface A new ConfigInterface object
*/
public function split(string $key): ConfigInterface
{
diff --git a/src/Exceptions/ConfigException.php b/src/Exceptions/ConfigException.php
index d9252c8..ce606ff 100644
--- a/src/Exceptions/ConfigException.php
+++ b/src/Exceptions/ConfigException.php
@@ -4,6 +4,4 @@
use Exception;
-abstract class ConfigException extends Exception
-{
-}
+abstract class ConfigException extends Exception {}
diff --git a/src/Exceptions/InvalidContextException.php b/src/Exceptions/InvalidContextException.php
index 29a8466..44ccc9f 100644
--- a/src/Exceptions/InvalidContextException.php
+++ b/src/Exceptions/InvalidContextException.php
@@ -2,6 +2,4 @@
namespace PHLAK\Config\Exceptions;
-class InvalidContextException extends ConfigException
-{
-}
+class InvalidContextException extends ConfigException {}
diff --git a/src/Exceptions/InvalidFileException.php b/src/Exceptions/InvalidFileException.php
index ad2eb1a..0084493 100644
--- a/src/Exceptions/InvalidFileException.php
+++ b/src/Exceptions/InvalidFileException.php
@@ -2,6 +2,4 @@
namespace PHLAK\Config\Exceptions;
-class InvalidFileException extends ConfigException
-{
-}
+class InvalidFileException extends ConfigException {}
diff --git a/src/Interfaces/ConfigInterface.php b/src/Interfaces/ConfigInterface.php
index 87b96d1..908060e 100644
--- a/src/Interfaces/ConfigInterface.php
+++ b/src/Interfaces/ConfigInterface.php
@@ -12,7 +12,7 @@ interface ConfigInterface
* more configuration files
* @param string $prefix A key under which the loaded config will be nested
*/
- public function __construct($context = null, string $prefix = null);
+ public function __construct(array|string $context = null, string $prefix = null);
/**
* Create a new instance of a ConfigInterface objet from a directory with
@@ -91,7 +91,6 @@ public function unset(string $key): bool;
* @param string $prefix A key under which the loaded config will be nested
* @param bool $override Whether to override existing options with
* values from the loaded file
- * @return ConfigInterface
*/
public function load(string $path, string $prefix = null, bool $override = true): self;
diff --git a/src/Loaders/Directory.php b/src/Loaders/Directory.php
index cb6a823..42494b2 100644
--- a/src/Loaders/Directory.php
+++ b/src/Loaders/Directory.php
@@ -3,7 +3,6 @@
namespace PHLAK\Config\Loaders;
use DirectoryIterator;
-use PHLAK\Config\Exceptions\InvalidFileException;
class Directory extends Loader
{
@@ -12,8 +11,6 @@ class Directory extends Loader
* and convert them to an array of configuration options. Any invalid files
* will be silently ignored.
*
- * @throws \PHLAK\Config\Exceptions\InvalidFileException
- *
* @return array Array of configuration options
*/
public function getArray(): array
@@ -28,13 +25,10 @@ public function getArray(): array
$className = $file->isDir() ? 'Directory' : ucfirst(strtolower($file->getExtension()));
$classPath = 'PHLAK\\Config\\Loaders\\' . $className;
+ /** @var Loader $loader */
$loader = new $classPath($file->getPathname());
- try {
- $contents = array_merge($contents, $loader->getArray());
- } catch (InvalidFileException $e) {
- // Ignore it and continue
- }
+ $contents = array_merge($contents, $loader->getArray());
}
return $contents;
diff --git a/src/Loaders/Json.php b/src/Loaders/Json.php
index e9552be..ef8bfed 100644
--- a/src/Loaders/Json.php
+++ b/src/Loaders/Json.php
@@ -18,6 +18,11 @@ public function getArray(): array
{
$contents = file_get_contents($this->context);
+ if ($contents === false) {
+ throw new InvalidFileException('Unable to parse invalid JSON file at ' . $this->context);
+ }
+
+ /** @var array|null $parsed */
$parsed = json_decode($contents, true);
if (is_null($parsed)) {
diff --git a/src/Loaders/Toml.php b/src/Loaders/Toml.php
index f4be8ea..0302bd1 100644
--- a/src/Loaders/Toml.php
+++ b/src/Loaders/Toml.php
@@ -19,6 +19,7 @@ class Toml extends Loader
public function getArray(): array
{
try {
+ /** @var array $parsed */
$parsed = TomlParser::parseFile($this->context);
} catch (ParseException $e) {
throw new InvalidFileException($e->getMessage());
diff --git a/src/Loaders/Xml.php b/src/Loaders/Xml.php
index dc6b892..fd1d3bb 100644
--- a/src/Loaders/Xml.php
+++ b/src/Loaders/Xml.php
@@ -2,6 +2,7 @@
namespace PHLAK\Config\Loaders;
+use JsonException;
use PHLAK\Config\Exceptions\InvalidFileException;
class Xml extends Loader
@@ -16,12 +17,21 @@ class Xml extends Loader
*/
public function getArray(): array
{
- $parsed = @simplexml_load_file($this->context);
+ $parsed = simplexml_load_file($this->context);
- if (! $parsed) {
+ if ($parsed === false) {
throw new InvalidFileException('Unable to parse invalid XML file at ' . $this->context);
}
- return json_decode(json_encode($parsed), true);
+ try {
+ $json = json_encode($parsed, flags: JSON_THROW_ON_ERROR);
+ } catch (JsonException $exception) {
+ throw new InvalidFileException(previous: $exception);
+ }
+
+ /** @var array $array */
+ $array = json_decode($json, true);
+
+ return $array;
}
}
diff --git a/src/Loaders/Yaml.php b/src/Loaders/Yaml.php
index 705fb35..b7b98fc 100644
--- a/src/Loaders/Yaml.php
+++ b/src/Loaders/Yaml.php
@@ -18,10 +18,16 @@ class Yaml extends Loader
*/
public function getArray(): array
{
+ $contents = file_get_contents($this->context);
+
+ if ($contents === false) {
+ throw new InvalidFileException(sprintf('Unable to parse file [%s]', $this->context));
+ }
+
try {
- $parsed = YamlParser::parse(file_get_contents($this->context));
- } catch (ParseException $e) {
- throw new InvalidFileException($e->getMessage());
+ $parsed = YamlParser::parse($contents);
+ } catch (ParseException $exception) {
+ throw new InvalidFileException($exception->getMessage());
}
if (! is_array($parsed)) {
diff --git a/src/Traits/Arrayable.php b/src/Traits/Arrayable.php
index 150ad17..b79fff9 100644
--- a/src/Traits/Arrayable.php
+++ b/src/Traits/Arrayable.php
@@ -20,11 +20,9 @@ public function getIterator(): Traversable
/**
* Determine whether an item exists at a specific offset.
*
- * @param int $offset Offset to check for existence
- *
- * @return bool
+ * @param mixed $offset Offset to check for existence
*/
- public function offsetExists($offset): bool
+ public function offsetExists(mixed $offset): bool
{
return isset($this->config[$offset]);
}
@@ -32,11 +30,9 @@ public function offsetExists($offset): bool
/**
* Retrieve an item at a specific offset.
*
- * @param int $offset Position of character to get
- *
- * @return mixed
+ * @param mixed $offset Position of character to get
*/
- public function offsetGet($offset): mixed
+ public function offsetGet(mixed $offset): mixed
{
return $this->config[$offset];
}
diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php
index 776654a..1fa4c72 100644
--- a/tests/ConfigTest.php
+++ b/tests/ConfigTest.php
@@ -13,14 +13,14 @@ class ConfigTest extends TestCase
{
public function test_it_is_instantiable(): void
{
- $config = new Config();
+ $config = new Config;
$this->assertInstanceOf(ConfigInterface::class, $config);
}
public function test_it_can_set_and_retrieve_an_item()
{
- $config = new Config();
+ $config = new Config;
$this->assertTrue($config->set('name', 'John Pinkerton'));
$this->assertEquals('John Pinkerton', $config->get('name'));
@@ -28,7 +28,7 @@ public function test_it_can_set_and_retrieve_an_item()
public function test_it_can_set_and_retrieve_an_item_by_dot_notation()
{
- $config = new Config();
+ $config = new Config;
$this->assertTrue($config->set('foo.bar.baz', 'foo-bar-baz'));
$this->assertEquals('foo-bar-baz', $config->get('foo.bar.baz'));
@@ -37,14 +37,14 @@ public function test_it_can_set_and_retrieve_an_item_by_dot_notation()
public function test_it_returns_null_for_nonexistant_items()
{
- $config = new Config();
+ $config = new Config;
$this->assertNull($config->get('nonexistant-item'));
}
public function test_it_returns_a_default_value_for_nonexistant_items()
{
- $config = new Config();
+ $config = new Config;
$this->assertFalse($config->get('nonexistant-item', false));
}
@@ -65,7 +65,7 @@ public function test_it_returns_true_if_it_has_a_boolean_false()
public function test_it_returns_false_if_it_doesnt_have_an_item()
{
- $config = new Config();
+ $config = new Config;
$this->assertFalse($config->has('nonexistant-item'));
}
@@ -88,7 +88,7 @@ public function test_it_can_load_and_read_additional_files()
public function test_it_can_load_additonal_files_with_a_prefix()
{
- $config = new Config();
+ $config = new Config;
$config->load(__DIR__ . '/files/php/config.php', 'database');
@@ -152,7 +152,7 @@ public function test_it_throws_an_exception_when_initialized_with_an_invalid_con
public function test_it_can_set_and_retrieve_a_closure()
{
- $config = new Config();
+ $config = new Config;
$config->set('closure', function ($foo) {
return ucwords($foo);