Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/Tests export-ignore
/phpstan.neon export-ignore
/phpunit.xml.dist export-ignore
40 changes: 40 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,43 @@ jobs:

- name: Run tests
run: vendor/bin/phpunit

phpstan:
name: PHPStan PHP ${{ matrix.php }} (Symfony ${{ matrix.symfony }})
runs-on: ubuntu-latest
strategy:
matrix:
php:
- '8.5'
symfony:
- '8.1.*'

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: flex
coverage: none

- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ matrix.php }}-composer-

- name: Update project dependencies
run: composer update --no-progress --ansi --prefer-stable
env:
SYMFONY_REQUIRE: ${{ matrix.symfony }}

- name: Run PHPStan
run: vendor/bin/phpstan analyze --error-format=github
3 changes: 3 additions & 0 deletions Partial/Exception/ChildIsNotAnArrayNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

class ChildIsNotAnArrayNode extends InvalidNodeNavigation
{
/**
* @param non-empty-string $nodeName
*/
public function __construct(BaseNode $parentNode, $nodeName)
{
parent::__construct(
Expand Down
3 changes: 3 additions & 0 deletions Partial/Exception/UndefinedChildNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

class UndefinedChildNode extends InvalidNodeNavigation
{
/**
* @param non-empty-string $childNodeName
*/
public function __construct(NodeInterface $parentNode, $childNodeName)
{
parent::__construct(
Expand Down
43 changes: 11 additions & 32 deletions Partial/PartialNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,14 @@

class PartialNode
{
/**
* @var \ReflectionProperty
*/
private static $nodeChildrenProperty;

/**
* @var \ReflectionProperty
*/
private static $nodePrototypeProperty;
private static ?\ReflectionProperty $nodeChildrenProperty = null;

/**
* Provide an ArrayNode instance (e.g. the root node created by a TreeBuilder) and a path that is relevant to you,
* e.g. "dbal.connections": this will strip every node that is not contained in the given path (e.g. the "orm" node
* would be removed entirely.
*
* @param ArrayNode $node
* @param string $breadcrumbPath
* @param string|null $breadcrumbPath
*/
public static function excludeEverythingNotInBreadcrumbPath(ArrayNode $node, $breadcrumbPath)
{
Expand All @@ -40,7 +31,10 @@ public static function excludeEverythingNotInBreadcrumbPath(ArrayNode $node, $br
}

/**
* @param array $path
* @param list<string> $path
*
* @throws ChildIsNotAnArrayNode if the child node is not an array node
* @throws UndefinedChildNode if the node does not have a child in the given path
*/
public static function excludeEverythingNotInPath(ArrayNode $node, array $path = [])
{
Expand Down Expand Up @@ -71,15 +65,14 @@ public static function excludeEverythingNotInPath(ArrayNode $node, array $path =
}

/**
* @param ArrayNode $node
* @param string $childNodeName
* @param non-empty-string $childNodeName
*
* @return NodeInterface
* @throws UndefinedChildNode if the node does not have a child with the given name
*/
private static function childNode(ArrayNode $node, $childNodeName)
private static function childNode(ArrayNode $node, string $childNodeName): NodeInterface
{
if ($node instanceof PrototypedArrayNode && '*' === $childNodeName) {
return self::nodePrototypeProperty()->getValue($node);
return $node->getPrototype();
}

$children = self::nodeChildrenProperty()->getValue($node);
Expand All @@ -94,25 +87,11 @@ private static function childNode(ArrayNode $node, $childNodeName)
return $children[$childNodeName];
}

/**
* @return \ReflectionProperty
*/
private static function nodeChildrenProperty()
private static function nodeChildrenProperty(): \ReflectionProperty
{
return self::$nodeChildrenProperty ??= new \ReflectionProperty(
ArrayNode::class,
'children'
);
}

/**
* @return \ReflectionProperty
*/
private static function nodePrototypeProperty()
{
return self::$nodePrototypeProperty ??= new \ReflectionProperty(
PrototypedArrayNode::class,
'prototype'
);
}
}
14 changes: 11 additions & 3 deletions Partial/PartialProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@

class PartialProcessor
{
/**
* @param string|null $breadcrumbPath
*
* @return array
*/
public function process(ArrayNode $node, $breadcrumbPath, array $configs)
{
PartialNode::excludeEverythingNotInBreadcrumbPath($node, $breadcrumbPath);

$processor = new Processor();

return $processor->process($node, $configs);
return (new Processor())->process($node, $configs);
}

/**
* @param string|null $breadcrumbPath
*
* @return array
*/
public function processConfiguration(ConfigurationInterface $configuration, $breadcrumbPath, array $configs)
{
return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $breadcrumbPath, $configs);
Expand Down
24 changes: 21 additions & 3 deletions PhpUnit/AbstractConfigurationConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,40 @@

abstract class AbstractConfigurationConstraint extends Constraint
{
/**
* @var ConfigurationInterface
*/
protected $configuration;

/**
* @var string|null
*/
protected $breadcrumbPath;

/**
* @param string|null $breadcrumbPath
*/
public function __construct(ConfigurationInterface $configuration, $breadcrumbPath = null)
{
$this->configuration = $configuration;
$this->breadcrumbPath = $breadcrumbPath;
}

/**
* @return array
*/
protected function processConfiguration(array $configurationValues)
{
$processor = new PartialProcessor();

return $processor->processConfiguration($this->configuration, $this->breadcrumbPath, $configurationValues);
return (new PartialProcessor())->processConfiguration($this->configuration, $this->breadcrumbPath, $configurationValues);
}

/**
* @param mixed $configurationValues
*
* @return void
*
* @throws \InvalidArgumentException if the configuration values structure is not the required shape
*/
protected function validateConfigurationValuesArray($configurationValues)
{
if (!is_array($configurationValues)) {
Expand Down
15 changes: 12 additions & 3 deletions PhpUnit/ConfigurationTestCaseTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
namespace Matthias\SymfonyConfigTest\PhpUnit;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
* Add this trait to your Test Case to add the ability of testing your configuration
* which should implement Symfony\Component\Config\Definition\ConfigurationInterface.
* which should implement {@see ConfigurationInterface}.
*/
trait ConfigurationTestCaseTrait
{
/**
* Return the instance of ConfigurationInterface that should be used by the
* Configuration-specific assertions in this test-case.
*
* @return \Symfony\Component\Config\Definition\ConfigurationInterface
* @return ConfigurationInterface
*/
abstract protected function getConfiguration();

Expand All @@ -29,6 +30,8 @@ abstract protected function getConfiguration();
* @param array $configurationValues
* @param string|null $expectedMessage
* @param bool $useRegExp
*
* @return void
*/
protected function assertConfigurationIsInvalid(array $configurationValues, $expectedMessage = null, $useRegExp = false)
{
Expand All @@ -51,9 +54,11 @@ protected function assertConfigurationIsInvalid(array $configurationValues, $exp
* to match the exception message using a regular expression.
*
* @param array $configurationValues
* @param string $breadcrumbPath The path that should be validated, e.g. "doctrine.orm"
* @param string|null $breadcrumbPath The path that should be validated, e.g. "doctrine.orm"
* @param string|null $expectedMessage
* @param bool $useRegExp
*
* @return void
*/
protected function assertPartialConfigurationIsInvalid(
array $configurationValues,
Expand All @@ -79,6 +84,8 @@ protected function assertPartialConfigurationIsInvalid(
*
* @param array $configurationValues
* @param string|null $breadcrumbPath
*
* @return void
*/
protected function assertConfigurationIsValid(array $configurationValues, $breadcrumbPath = null)
{
Expand All @@ -99,6 +106,8 @@ protected function assertConfigurationIsValid(array $configurationValues, $bread
* @param array $configurationValues
* @param array $expectedProcessedConfiguration
* @param string|null $breadcrumbPath
*
* @return void
*/
protected function assertProcessedConfigurationEquals(
array $configurationValues,
Expand Down
25 changes: 23 additions & 2 deletions PhpUnit/ConfigurationValuesAreInvalidConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Matthias\SymfonyConfigTest\PhpUnit;

use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\Constraint\ExceptionMessage;
use PHPUnit\Framework\Constraint\ExceptionMessageIsOrContains;
use PHPUnit\Framework\Constraint\ExceptionMessageMatchesRegularExpression;
Expand All @@ -13,9 +14,21 @@

class ConfigurationValuesAreInvalidConstraint extends AbstractConfigurationConstraint
{
/**
* @var string|null
*/
private $expectedMessage;

/**
* @var bool
*/
private $useRegExp;

/**
* @param string|null $expectedMessage
* @param bool $useRegExp
* @param string|null $breadcrumbPath
*/
public function __construct(
ConfigurationInterface $configuration,
$expectedMessage = null,
Expand All @@ -28,6 +41,9 @@ public function __construct(
$this->useRegExp = $useRegExp;
}

/**
* {@inheritdoc}
*/
public function evaluate($other, $description = '', $returnResult = false): ?bool
{
$this->validateConfigurationValuesArray($other);
Expand All @@ -44,6 +60,7 @@ public function evaluate($other, $description = '', $returnResult = false): ?boo

$this->fail($other, $description);

/** @phpstan-ignore deadCode.unreachable (legacy B/C layer) */
return null;
}

Expand All @@ -58,7 +75,7 @@ public function toString(): string
return $toString;
}

private function evaluateException(\Exception $exception, $description, $returnResult)
private function evaluateException(\Exception $exception, string $description = '', bool $returnResult = false): ?bool
{
if ($this->expectedMessage === null) {
return true;
Expand All @@ -68,7 +85,7 @@ private function evaluateException(\Exception $exception, $description, $returnR
->evaluate($exception->getMessage(), $description, $returnResult);
}

private function createPhpUnitConstraint()
private function createPhpUnitConstraint(): Constraint
{
if ($this->useRegExp) {
// Available since PHPUnit 10.0.15
Expand All @@ -78,10 +95,12 @@ private function createPhpUnitConstraint()

// Available between PHPUnit 10.0.0 and 10.0.14 (inclusive)
if (class_exists(MessageMatchesRegularExpression::class)) {
/** @phpstan-ignore return.type (legacy B/C layer) */
return new MessageMatchesRegularExpression('exception', $this->expectedMessage);
}

// Available in PHPUnit 9.6
/** @phpstan-ignore class.notFound,return.type (legacy B/C layer) */
return new ExceptionMessageRegularExpression($this->expectedMessage);
}

Expand All @@ -92,10 +111,12 @@ private function createPhpUnitConstraint()

// Available between PHPUnit 10.0.0 and 10.0.14 (inclusive)
if (class_exists(MessageIsOrContains::class)) {
/** @phpstan-ignore return.type (legacy B/C layer) */
return new MessageIsOrContains('exception', $this->expectedMessage);
}

// Available in PHPUnit 9.6
/** @phpstan-ignore class.notFound,return.type (legacy B/C layer) */
return new ExceptionMessage($this->expectedMessage);
}
}
6 changes: 0 additions & 6 deletions PhpUnit/ConfigurationValuesAreValidConstraint.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@

namespace Matthias\SymfonyConfigTest\PhpUnit;

use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;

class ConfigurationValuesAreValidConstraint extends AbstractConfigurationConstraint
{
public function __construct(ConfigurationInterface $configuration, $breadcrumbPath = null)
{
parent::__construct($configuration, $breadcrumbPath);
}

public function evaluate($other, $description = '', $returnResult = false): ?bool
{
$this->validateConfigurationValuesArray($other);
Expand Down
Loading
Loading