diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 4d9f3d8..3d3e015 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -76,14 +76,14 @@
-
+
-
+
-
+
-
+
diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php
index 3e79a3b..cb27092 100644
--- a/src/ConfigProvider.php
+++ b/src/ConfigProvider.php
@@ -23,8 +23,6 @@
use Kynx\Laminas\FormShape\Form\FormVisitorFactory;
use Kynx\Laminas\FormShape\InputFilter\ArrayInputVisitor;
use Kynx\Laminas\FormShape\InputFilter\ArrayInputVisitorFactory;
-use Kynx\Laminas\FormShape\InputFilter\CollectionInputVisitor;
-use Kynx\Laminas\FormShape\InputFilter\CollectionInputVisitorFactory;
use Kynx\Laminas\FormShape\InputFilter\InputFilterVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputFilterVisitorFactory;
use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
@@ -158,7 +156,6 @@ private function getLaminasFormShapeConfig(): array
],
'input-visitors' => [
ArrayInputVisitor::class,
- CollectionInputVisitor::class,
InputVisitor::class,
],
'filter' => [
@@ -222,24 +219,23 @@ private function getDependencyConfig(): array
TypeNamerInterface::class => TypeNamer::class,
],
'factories' => [
- AllowListVisitor::class => AllowListVisitorFactory::class,
- ArrayInputVisitor::class => ArrayInputVisitorFactory::class,
- CollectionInputVisitor::class => CollectionInputVisitorFactory::class,
- ExplodeVisitor::class => ExplodeVisitorFactory::class,
- FileValidatorVisitor::class => FileValidatorVisitorFactory::class,
- FormLocator::class => FormLocatorFactory::class,
- FormProcessor::class => FormProcessorFactory::class,
- NetteCodeGenerator::class => NetteCodeGeneratorFactory::class,
- PrettyPrinter::class => PrettyPrinterFactory::class,
- PsalmTypeCommand::class => PsalmTypeCommandFactory::class,
- FileWriter::class => FileWriterFactory::class,
- FormVisitor::class => FormVisitorFactory::class,
- InArrayVisitor::class => InArrayVisitorFactory::class,
- InputFilterVisitor::class => InputFilterVisitorFactory::class,
- InputVisitor::class => InputVisitorFactory::class,
- NonEmptyStringVisitor::class => NonEmptyStringVisitorFactory::class,
- RegexVisitor::class => RegexVisitorFactory::class,
- TypeNamer::class => TypeNamerFactory::class,
+ AllowListVisitor::class => AllowListVisitorFactory::class,
+ ArrayInputVisitor::class => ArrayInputVisitorFactory::class,
+ ExplodeVisitor::class => ExplodeVisitorFactory::class,
+ FileValidatorVisitor::class => FileValidatorVisitorFactory::class,
+ FormLocator::class => FormLocatorFactory::class,
+ FormProcessor::class => FormProcessorFactory::class,
+ NetteCodeGenerator::class => NetteCodeGeneratorFactory::class,
+ PrettyPrinter::class => PrettyPrinterFactory::class,
+ PsalmTypeCommand::class => PsalmTypeCommandFactory::class,
+ FileWriter::class => FileWriterFactory::class,
+ FormVisitor::class => FormVisitorFactory::class,
+ InArrayVisitor::class => InArrayVisitorFactory::class,
+ InputFilterVisitor::class => InputFilterVisitorFactory::class,
+ InputVisitor::class => InputVisitorFactory::class,
+ NonEmptyStringVisitor::class => NonEmptyStringVisitorFactory::class,
+ RegexVisitor::class => RegexVisitorFactory::class,
+ TypeNamer::class => TypeNamerFactory::class,
],
];
}
diff --git a/src/Form/FormVisitor.php b/src/Form/FormVisitor.php
index b9cc25e..67e76b4 100644
--- a/src/Form/FormVisitor.php
+++ b/src/Form/FormVisitor.php
@@ -143,7 +143,12 @@ private function convertCollectionFilters(
$count = $required ? $elementOrFieldset->getCount() : 0;
if ($target instanceof InputInterface) {
- $inputOrFilter = CollectionInput::fromInput($target, $count);
+ /** @psalm-suppress TooFewArguments It thinks `getRawValue()` takes an argument ?! */
+ $target->setValue([$target->getRawValue()]);
+
+ $inputOrFilter = new CollectionInput($target->getName());
+ $inputOrFilter->merge($target);
+ $inputOrFilter->setCount($count);
} else {
$inputOrFilter = new CollectionInputFilter();
$inputOrFilter->setIsRequired($required);
diff --git a/src/InputFilter/AbstractInputVisitor.php b/src/InputFilter/AbstractInputVisitor.php
new file mode 100644
index 0000000..0067042
--- /dev/null
+++ b/src/InputFilter/AbstractInputVisitor.php
@@ -0,0 +1,116 @@
+ $filterVisitors
+ * @param array $validatorVisitors
+ */
+ public function __construct(protected array $filterVisitors, protected array $validatorVisitors)
+ {
+ }
+
+ protected function visitInput(InputInterface $input, Union $initial): Union
+ {
+ $union = $initial->getBuilder()->freeze();
+
+ foreach ($input->getFilterChain()->getIterator() as $filter) {
+ if (! $filter instanceof FilterInterface) {
+ continue;
+ }
+ $union = $this->visitFilters($filter, $union);
+ }
+
+ $validators = $this->prependNotEmptyValidator($input, array_map(
+ static fn (array $queueItem): ValidatorInterface => $queueItem['instance'],
+ $input->getValidatorChain()->getValidators()
+ ));
+
+ foreach ($validators as $validator) {
+ $union = $this->visitValidators($validator, $union);
+ }
+
+ if (! $this->continueIfEmpty($input) && ($input->allowEmpty() || ! $input->isRequired())) {
+ $union = TypeUtil::widen($union, $initial);
+ }
+
+ return $union;
+ }
+
+ /**
+ * @psalm-assert-if-true Input $input
+ */
+ protected function hasFallback(InputInterface $input): bool
+ {
+ return $input instanceof Input && $input->hasFallback();
+ }
+
+ private function continueIfEmpty(InputInterface $input): bool
+ {
+ return $input instanceof EmptyContextInterface && $input->continueIfEmpty();
+ }
+
+ /**
+ * @param array $validators
+ * @return array
+ */
+ private function prependNotEmptyValidator(InputInterface $input, array $validators): array
+ {
+ $hasNotEmpty = (bool) array_filter(
+ $validators,
+ static fn (ValidatorInterface $validator): bool => $validator instanceof NotEmpty
+ );
+ if ($hasNotEmpty) {
+ return $validators;
+ }
+
+ /**
+ * There's some weirdness here: on the default `Text` element, upstream `''` validates, but `' '` fails. So
+ * while I _think_ this should be `! $continueIfEmpty && ($input->isRequired() || ! $input->allowEmpty())`, it
+ * can't be. And I shudder to think of the mayhem it would cause if I raised it as a bug ;)
+ */
+ if (! $this->continueIfEmpty($input) && $input->isRequired() && ! $input->allowEmpty()) {
+ array_unshift($validators, new NotEmpty());
+ }
+
+ return $validators;
+ }
+
+ private function visitFilters(FilterInterface $filter, Union $union): Union
+ {
+ foreach ($this->filterVisitors as $visitor) {
+ $union = $visitor->visit($filter, $union);
+ }
+
+ return $union;
+ }
+
+ private function visitValidators(ValidatorInterface $validator, Union $union): Union
+ {
+ foreach ($this->validatorVisitors as $visitor) {
+ $union = $visitor->visit($validator, $union);
+ }
+
+ return $union;
+ }
+}
diff --git a/src/InputFilter/AbstractInputVisitorFactory.php b/src/InputFilter/AbstractInputVisitorFactory.php
new file mode 100644
index 0000000..ca6ed95
--- /dev/null
+++ b/src/InputFilter/AbstractInputVisitorFactory.php
@@ -0,0 +1,68 @@
+
+ */
+ protected function getFilterVisitors(ContainerInterface $container): array
+ {
+ /** @var FormShapeConfigurationArray $config */
+ $config = $container->get('config') ?? [];
+
+ $filterVisitors = [];
+ foreach ($config['laminas-form-shape']['filter-visitors'] as $visitorName) {
+ $visitor = $this->getVisitor($container, $visitorName);
+ $filterVisitors[] = $visitor;
+ }
+
+ return $filterVisitors;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getValidatorVisitors(ContainerInterface $container): array
+ {
+ /** @var FormShapeConfigurationArray $config */
+ $config = $container->get('config') ?? [];
+
+ $validatorVisitors = [];
+ foreach ($config['laminas-form-shape']['validator-visitors'] as $visitorName) {
+ $visitor = $this->getVisitor($container, $visitorName);
+ $validatorVisitors[] = $visitor;
+ }
+
+ return $validatorVisitors;
+ }
+
+ /**
+ * @template T of FilterVisitorInterface|ValidatorVisitorInterface
+ * @param class-string $visitorName
+ * @return T
+ */
+ private function getVisitor(
+ ContainerInterface $container,
+ string $visitorName
+ ): FilterVisitorInterface|ValidatorVisitorInterface {
+ if ($container->has($visitorName)) {
+ return $container->get($visitorName);
+ }
+
+ return new $visitorName();
+ }
+}
diff --git a/src/InputFilter/ArrayInputVisitor.php b/src/InputFilter/ArrayInputVisitor.php
index c0bac49..5c51d0f 100644
--- a/src/InputFilter/ArrayInputVisitor.php
+++ b/src/InputFilter/ArrayInputVisitor.php
@@ -4,32 +4,58 @@
namespace Kynx\Laminas\FormShape\InputFilter;
-use Kynx\Laminas\FormShape\InputVisitorInterface;
+use Kynx\Laminas\FormShape\Psalm\TypeUtil;
use Laminas\InputFilter\ArrayInput;
use Laminas\InputFilter\InputInterface;
use Psalm\Type;
+use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TNonEmptyArray;
+use Psalm\Type\Atomic\TNull;
+use Psalm\Type\Atomic\TString;
use Psalm\Type\Union;
-final readonly class ArrayInputVisitor implements InputVisitorInterface
-{
- public function __construct(private InputVisitor $inputVisitor)
- {
- }
+use function array_map;
+final readonly class ArrayInputVisitor extends AbstractInputVisitor
+{
public function visit(InputInterface $input): ?Union
{
- if (! ($input instanceof ArrayInput || $input instanceof CollectionInput)) {
+ if (! $input instanceof ArrayInput) {
return null;
}
- // @fixme Is the logic here exactly the same?
- $union = $this->inputVisitor->visit($input);
- $array = $input->isRequired()
- ? new TNonEmptyArray([Type::getArrayKey(), new Union($union->getAtomicTypes())])
- : new TArray([Type::getArrayKey(), new Union($union->getAtomicTypes())]);
+ $initial = new Union([new TNull(), new TString()]);
+ $union = $this->visitInput($input, $initial);
- return new Union([$array], ['possibly_undefined' => $union->possibly_undefined]);
+ if ($union->getAtomicTypes() === []) {
+ throw InputVisitorException::cannotGetInputType($input);
+ }
+
+ $union = new Union([new TArray([Type::getArrayKey(), $union])]);
+
+ if ($this->isNonEmpty($input)) {
+ $nonEmpty = array_map(
+ static fn (Atomic $type): Atomic => $type instanceof TArray
+ ? new TNonEmptyArray($type->type_params)
+ : $type,
+ $union->getAtomicTypes()
+ );
+ $union = new Union($nonEmpty);
+ }
+
+ if ($this->hasFallback($input)) {
+ return Type::combineUnionTypes($union, TypeUtil::toStrictUnion($input->getFallbackValue()));
+ }
+
+ return $union;
+ }
+
+ private function isNonEmpty(ArrayInput $input): bool
+ {
+ if ($input instanceof CollectionInput) {
+ return (bool) $input->getCount();
+ }
+ return $input->isRequired();
}
}
diff --git a/src/InputFilter/ArrayInputVisitorFactory.php b/src/InputFilter/ArrayInputVisitorFactory.php
index 42dc53e..294bb4b 100644
--- a/src/InputFilter/ArrayInputVisitorFactory.php
+++ b/src/InputFilter/ArrayInputVisitorFactory.php
@@ -6,10 +6,13 @@
use Psr\Container\ContainerInterface;
-final readonly class ArrayInputVisitorFactory
+final readonly class ArrayInputVisitorFactory extends AbstractInputVisitorFactory
{
public function __invoke(ContainerInterface $container): ArrayInputVisitor
{
- return new ArrayInputVisitor($container->get(InputVisitor::class));
+ return new ArrayInputVisitor(
+ $this->getFilterVisitors($container),
+ $this->getValidatorVisitors($container)
+ );
}
}
diff --git a/src/InputFilter/CollectionInput.php b/src/InputFilter/CollectionInput.php
index ac447e9..f699001 100644
--- a/src/InputFilter/CollectionInput.php
+++ b/src/InputFilter/CollectionInput.php
@@ -4,190 +4,28 @@
namespace Kynx\Laminas\FormShape\InputFilter;
-use Laminas\Filter\FilterChain;
-use Laminas\InputFilter\EmptyContextInterface;
-use Laminas\InputFilter\InputInterface;
-use Laminas\Validator\ValidatorChain;
-
-use function assert;
-use function is_array;
+use Laminas\InputFilter\ArrayInput;
/**
- * Input for handling form collections where the target element is an actual element, not a fieldset
- *
- * This is not designed for real-world validation; it's purpose is to capture the `count` and (very probably)
- * `possibly_undefined` state of the collection, but otherwise proxy the element's actual `InputInterface`.
+ * Specialised input for representing collections with non-fieldset target elements
*
* @internal
*
- * @see CollectionInputVisitor
- *
* @psalm-internal Kynx\Laminas\FormShape
* @psalm-internal KynxTest\Laminas\FormShape
*/
-final readonly class CollectionInput implements InputInterface, EmptyContextInterface
+final class CollectionInput extends ArrayInput
{
- private function __construct(private InputInterface $delegate, private int $count)
- {
- }
+ private int $count = 0;
- public static function fromInput(InputInterface $input, int $count): self
+ public function setCount(int $count): self
{
- return new self($input, $count);
+ $this->count = $count;
+ return $this;
}
public function getCount(): int
{
return $this->count;
}
-
- /**
- * @param bool $continueIfEmpty
- */
- public function setContinueIfEmpty($continueIfEmpty): self
- {
- if ($this->delegate instanceof EmptyContextInterface) {
- $this->delegate->setContinueIfEmpty($continueIfEmpty);
- }
- return $this;
- }
-
- public function continueIfEmpty(): bool
- {
- if ($this->delegate instanceof EmptyContextInterface) {
- return $this->delegate->continueIfEmpty();
- }
- return false;
- }
-
- /**
- * @param bool $allowEmpty
- */
- public function setAllowEmpty($allowEmpty): self
- {
- $this->delegate->setAllowEmpty($allowEmpty);
- return $this;
- }
-
- /**
- * @param bool $breakOnFailure
- */
- public function setBreakOnFailure($breakOnFailure): self
- {
- $this->delegate->setBreakOnFailure($breakOnFailure);
- return $this;
- }
-
- /**
- * @param null|string $errorMessage
- */
- public function setErrorMessage($errorMessage): self
- {
- $this->delegate->setErrorMessage($errorMessage);
- return $this;
- }
-
- public function setFilterChain(FilterChain $filterChain): self
- {
- $this->delegate->setFilterChain($filterChain);
- return $this;
- }
-
- /**
- * @param string $name
- */
- public function setName($name): self
- {
- $this->delegate->setName($name);
- return $this;
- }
-
- /**
- * @param bool $required
- */
- public function setRequired($required): self
- {
- $this->delegate->setRequired($required);
- return $this;
- }
-
- public function setValidatorChain(ValidatorChain $validatorChain): self
- {
- $this->delegate->setValidatorChain($validatorChain);
- return $this;
- }
-
- /**
- * @param mixed $value
- */
- public function setValue($value): self
- {
- assert(is_array($value));
- $this->delegate->setValue($value);
- return $this;
- }
-
- public function merge(InputInterface $input): self
- {
- $this->delegate->merge($input);
- return $this;
- }
-
- public function allowEmpty(): bool
- {
- return $this->delegate->allowEmpty();
- }
-
- public function breakOnFailure(): bool
- {
- return $this->delegate->breakOnFailure();
- }
-
- public function getErrorMessage(): ?string
- {
- return $this->delegate->getErrorMessage();
- }
-
- public function getFilterChain(): FilterChain
- {
- return $this->delegate->getFilterChain();
- }
-
- public function getName(): string
- {
- return $this->delegate->getName();
- }
-
- public function getRawValue(): mixed
- {
- return $this->delegate->getRawValue();
- }
-
- public function isRequired(): bool
- {
- return $this->delegate->isRequired();
- }
-
- public function getValidatorChain(): ValidatorChain
- {
- return $this->delegate->getValidatorChain();
- }
-
- public function getValue(): mixed
- {
- return $this->delegate->getValue();
- }
-
- /**
- * @param mixed $context
- */
- public function isValid($context = null): bool
- {
- return $this->delegate->isValid();
- }
-
- public function getMessages(): array
- {
- return $this->delegate->getMessages();
- }
}
diff --git a/src/InputFilter/CollectionInputVisitor.php b/src/InputFilter/CollectionInputVisitor.php
deleted file mode 100644
index 773150e..0000000
--- a/src/InputFilter/CollectionInputVisitor.php
+++ /dev/null
@@ -1,33 +0,0 @@
-inputVisitor->visit($input);
- $array = $input->getCount() > 0
- ? new TNonEmptyArray([Type::getArrayKey(), new Union($union->getAtomicTypes())])
- : new TArray([Type::getArrayKey(), new Union($union->getAtomicTypes())]);
-
- return new Union([$array]);
- }
-}
diff --git a/src/InputFilter/CollectionInputVisitorFactory.php b/src/InputFilter/CollectionInputVisitorFactory.php
deleted file mode 100644
index 09a2e07..0000000
--- a/src/InputFilter/CollectionInputVisitorFactory.php
+++ /dev/null
@@ -1,15 +0,0 @@
-get(InputVisitor::class));
- }
-}
diff --git a/src/InputFilter/InputVisitor.php b/src/InputFilter/InputVisitor.php
index 385b649..c509310 100644
--- a/src/InputFilter/InputVisitor.php
+++ b/src/InputFilter/InputVisitor.php
@@ -4,71 +4,22 @@
namespace Kynx\Laminas\FormShape\InputFilter;
-use Kynx\Laminas\FormShape\FilterVisitorInterface;
use Kynx\Laminas\FormShape\InputFilter\InputVisitorException;
-use Kynx\Laminas\FormShape\InputVisitorInterface;
use Kynx\Laminas\FormShape\Psalm\TypeUtil;
-use Kynx\Laminas\FormShape\ValidatorVisitorInterface;
-use Laminas\Filter\FilterInterface;
-use Laminas\InputFilter\EmptyContextInterface;
-use Laminas\InputFilter\Input;
use Laminas\InputFilter\InputInterface;
-use Laminas\Validator\NotEmpty;
-use Laminas\Validator\ValidatorInterface;
use Psalm\Type;
use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Union;
-use function array_map;
-use function array_unshift;
-
-final readonly class InputVisitor implements InputVisitorInterface
+final readonly class InputVisitor extends AbstractInputVisitor
{
- /**
- * @param array $filterVisitors
- * @param array $validatorVisitors
- */
- public function __construct(private array $filterVisitors, private array $validatorVisitors)
- {
- }
-
public function visit(InputInterface $input): Union
{
- $hasFallback = $input instanceof Input && $input->hasFallback();
- $union = new Union([new TNull(), new TString()]);
-
- foreach ($input->getFilterChain()->getIterator() as $filter) {
- if (! $filter instanceof FilterInterface) {
- continue;
- }
- $union = $this->visitFilters($filter, $union);
- }
-
- $validators = array_map(
- static fn (array $queueItem): ValidatorInterface => $queueItem['instance'],
- $input->getValidatorChain()->getValidators()
- );
-
- $continueIfEmpty = $input instanceof EmptyContextInterface && $input->continueIfEmpty();
- /**
- * There's some weirdness here: on the default `Text` element, upstream `''` validates, but `' '` fails. So
- * while I _think_ this should be `! $continueIfEmpty && ($input->isRequired() || ! $input->allowEmpty())`, it
- * can't be. And I shudder to think of the mayhem it would cause if I raised it as a bug ;)
- */
- if (! $continueIfEmpty && $input->isRequired() && ! $input->allowEmpty()) {
- array_unshift($validators, new NotEmpty());
- }
-
- foreach ($validators as $validator) {
- $union = $this->visitValidators($validator, $union);
- }
-
- if (! $continueIfEmpty && ($input->allowEmpty() || ! $input->isRequired())) {
- $union = TypeUtil::widen($union, new Union([new TString(), new TNull()]));
- }
+ $initial = new Union([new TNull(), new TString()]);
+ $union = $this->visitInput($input, $initial);
- if ($input instanceof Input && $hasFallback) {
+ if ($this->hasFallback($input)) {
$union = Type::combineUnionTypes($union, TypeUtil::toStrictUnion($input->getFallbackValue()));
}
@@ -78,22 +29,4 @@ public function visit(InputInterface $input): Union
return $union;
}
-
- private function visitFilters(FilterInterface $filter, Union $union): Union
- {
- foreach ($this->filterVisitors as $visitor) {
- $union = $visitor->visit($filter, $union);
- }
-
- return $union;
- }
-
- private function visitValidators(ValidatorInterface $validator, Union $union): Union
- {
- foreach ($this->validatorVisitors as $visitor) {
- $union = $visitor->visit($validator, $union);
- }
-
- return $union;
- }
}
diff --git a/src/InputFilter/InputVisitorFactory.php b/src/InputFilter/InputVisitorFactory.php
index 1220439..56e670f 100644
--- a/src/InputFilter/InputVisitorFactory.php
+++ b/src/InputFilter/InputVisitorFactory.php
@@ -4,52 +4,15 @@
namespace Kynx\Laminas\FormShape\InputFilter;
-use Kynx\Laminas\FormShape\ConfigProvider;
-use Kynx\Laminas\FormShape\FilterVisitorInterface;
-use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
-use Kynx\Laminas\FormShape\ValidatorVisitorInterface;
use Psr\Container\ContainerInterface;
-use function assert;
-
-/**
- * @psalm-import-type FormShapeConfigurationArray from ConfigProvider
- */
-final readonly class InputVisitorFactory
+final readonly class InputVisitorFactory extends AbstractInputVisitorFactory
{
public function __invoke(ContainerInterface $container): InputVisitor
{
- /** @var FormShapeConfigurationArray $config */
- $config = $container->get('config') ?? [];
-
- $filterVisitors = [];
- foreach ($config['laminas-form-shape']['filter-visitors'] as $visitorName) {
- $visitor = $this->getVisitor($container, $visitorName);
- assert($visitor instanceof FilterVisitorInterface);
- $filterVisitors[] = $visitor;
- }
-
- $validatorVisitors = [];
- foreach ($config['laminas-form-shape']['validator-visitors'] as $visitorName) {
- $visitor = $this->getVisitor($container, $visitorName);
- assert($visitor instanceof ValidatorVisitorInterface);
- $validatorVisitors[] = $visitor;
- }
-
- return new InputVisitor($filterVisitors, $validatorVisitors);
- }
-
- /**
- * @param class-string $visitorName
- */
- private function getVisitor(
- ContainerInterface $container,
- string $visitorName
- ): FilterVisitorInterface|ValidatorVisitorInterface {
- if ($container->has($visitorName)) {
- return $container->get($visitorName);
- }
-
- return new $visitorName();
+ return new InputVisitor(
+ $this->getFilterVisitors($container),
+ $this->getValidatorVisitors($container)
+ );
}
}
diff --git a/test/Form/FormCollectionSmokeTest.php b/test/Form/FormCollectionSmokeTest.php
index c748daf..b6fe330 100644
--- a/test/Form/FormCollectionSmokeTest.php
+++ b/test/Form/FormCollectionSmokeTest.php
@@ -6,7 +6,7 @@
use Kynx\Laminas\FormShape\Decorator\PrettyPrinter;
use Kynx\Laminas\FormShape\Form\FormVisitor;
-use Kynx\Laminas\FormShape\InputFilter\CollectionInputVisitor;
+use Kynx\Laminas\FormShape\InputFilter\ArrayInputVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputFilterVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
use Kynx\Laminas\FormShape\Psalm\ConfigLoader;
@@ -34,10 +34,10 @@ protected function setUp(): void
{
parent::setUp();
- $inputVisitor = new InputVisitor([], [new NotEmptyVisitor()]);
- $collectionInputVisitor = new CollectionInputVisitor($inputVisitor);
- $inputFilterVisitor = new InputFilterVisitor([
- $collectionInputVisitor,
+ $inputVisitor = new InputVisitor([], [new NotEmptyVisitor()]);
+ $arrayInputVisitor = new ArrayInputVisitor([], [new NotEmptyVisitor()]);
+ $inputFilterVisitor = new InputFilterVisitor([
+ $arrayInputVisitor,
$inputVisitor,
]);
diff --git a/test/Form/FormElementSmokeTest.php b/test/Form/FormElementSmokeTest.php
index c465514..cdc2c63 100644
--- a/test/Form/FormElementSmokeTest.php
+++ b/test/Form/FormElementSmokeTest.php
@@ -239,4 +239,21 @@ public static function defaultElementProvider(): array
],
];
}
+
+ public function testMultiCheckboxValidatesSingleString(): void
+ {
+ $form = new Form();
+ $multiCheckbox = new MultiCheckbox('foo', ['value_options' => [1 => 'a', 2 => 'b']]);
+ $form->add($multiCheckbox);
+
+ $form->setData(['foo' => '1']);
+ $isValid = $form->isValid();
+ self::assertTrue($isValid);
+ $data = $form->getData();
+ self::assertSame(['foo' => '1'], $data);
+
+ $form->setData(['foo' => ['1', '2']]);
+ $isValid = $form->isValid();
+ self::assertTrue($isValid);
+ }
}
diff --git a/test/Form/FormFieldsetSmokeTest.php b/test/Form/FormFieldsetSmokeTest.php
index 56f6ec6..a7a24fd 100644
--- a/test/Form/FormFieldsetSmokeTest.php
+++ b/test/Form/FormFieldsetSmokeTest.php
@@ -6,7 +6,7 @@
use Kynx\Laminas\FormShape\Decorator\PrettyPrinter;
use Kynx\Laminas\FormShape\Form\FormVisitor;
-use Kynx\Laminas\FormShape\InputFilter\CollectionInputVisitor;
+use Kynx\Laminas\FormShape\InputFilter\ArrayInputVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputFilterVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
use Kynx\Laminas\FormShape\Psalm\ConfigLoader;
@@ -32,10 +32,10 @@ protected function setUp(): void
{
parent::setUp();
- $inputVisitor = new InputVisitor([], [new NotEmptyVisitor()]);
- $collectionInputVisitor = new CollectionInputVisitor($inputVisitor);
- $inputFilterVisitor = new InputFilterVisitor([
- $collectionInputVisitor,
+ $inputVisitor = new InputVisitor([], [new NotEmptyVisitor()]);
+ $arrayInputVisitor = new ArrayInputVisitor([], [new NotEmptyVisitor()]);
+ $inputFilterVisitor = new InputFilterVisitor([
+ $arrayInputVisitor,
$inputVisitor,
]);
diff --git a/test/Form/FormProcessorTest.php b/test/Form/FormProcessorTest.php
index 5996c8e..89e9b65 100644
--- a/test/Form/FormProcessorTest.php
+++ b/test/Form/FormProcessorTest.php
@@ -6,7 +6,7 @@
use Kynx\Laminas\FormShape\Form\FormProcessor;
use Kynx\Laminas\FormShape\Form\FormVisitor;
-use Kynx\Laminas\FormShape\InputFilter\CollectionInputVisitor;
+use Kynx\Laminas\FormShape\InputFilter\ArrayInputVisitor;
use Kynx\Laminas\FormShape\InputFilter\ImportType;
use Kynx\Laminas\FormShape\InputFilter\InputFilterVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
@@ -52,11 +52,11 @@ protected function setUp(): void
$this->listener = new MockProgressListener();
$inputVisitor = new InputVisitor([], []);
- $collectionVisitor = new CollectionInputVisitor($inputVisitor);
+ $arrayInputVisitor = new ArrayInputVisitor([], []);
$this->processor = new FormProcessor(
$this->formLocator,
- new FormVisitor(new InputFilterVisitor([$collectionVisitor, $inputVisitor])),
+ new FormVisitor(new InputFilterVisitor([$arrayInputVisitor, $inputVisitor])),
$this->fileWriter
);
}
diff --git a/test/Form/FormVisitorTest.php b/test/Form/FormVisitorTest.php
index 33f490f..a3f09f9 100644
--- a/test/Form/FormVisitorTest.php
+++ b/test/Form/FormVisitorTest.php
@@ -5,10 +5,11 @@
namespace KynxTest\Laminas\FormShape\Form;
use Kynx\Laminas\FormShape\Form\FormVisitor;
-use Kynx\Laminas\FormShape\InputFilter\CollectionInputVisitor;
+use Kynx\Laminas\FormShape\InputFilter\ArrayInputVisitor;
use Kynx\Laminas\FormShape\InputFilter\ImportType;
use Kynx\Laminas\FormShape\InputFilter\InputFilterVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
+use Kynx\Laminas\FormShape\Psalm\ConfigLoader;
use KynxTest\Laminas\FormShape\Form\Asset\InputFilterFieldset;
use Laminas\Form\Element\Collection;
use Laminas\Form\Element\Email;
@@ -39,9 +40,11 @@ protected function setUp(): void
{
parent::setUp();
+ ConfigLoader::load();
+
$inputVisitor = new InputVisitor([], []);
- $collectionVisitor = new CollectionInputVisitor($inputVisitor);
- $this->visitor = new FormVisitor(new InputFilterVisitor([$collectionVisitor, $inputVisitor]));
+ $arrayInputVisitor = new ArrayInputVisitor([], []);
+ $this->visitor = new FormVisitor(new InputFilterVisitor([$arrayInputVisitor, $inputVisitor]));
}
public function testVisitSingleElement(): void
diff --git a/test/InputFilter/AbstractInputVisitorFactoryTest.php b/test/InputFilter/AbstractInputVisitorFactoryTest.php
new file mode 100644
index 0000000..b045090
--- /dev/null
+++ b/test/InputFilter/AbstractInputVisitorFactoryTest.php
@@ -0,0 +1,45 @@
+createStub(ContainerInterface::class);
+ $container->method('get')
+ ->willReturnMap([
+ ['config', $this->getConfig([AllowListVisitor::class], [BetweenVisitor::class])],
+ [AllowListVisitor::class, $allowListVisitor],
+ [BetweenVisitor::class, $betweenVisitor],
+ ]);
+
+ $factory = new MockAbstractInputVisitorFactory();
+ $instance = $factory($container);
+
+ self::assertEquals([$allowListVisitor], $instance->getFilterVisitors());
+ self::assertEquals([$betweenVisitor], $instance->getValidatorVisitors());
+ }
+
+ private function getConfig(array $filterVisitors, array $validatorVisitors): array
+ {
+ return [
+ 'laminas-form-shape' => [
+ 'filter-visitors' => $filterVisitors,
+ 'validator-visitors' => $validatorVisitors,
+ ],
+ ];
+ }
+}
diff --git a/test/InputFilter/AbstractInputVisitorTest.php b/test/InputFilter/AbstractInputVisitorTest.php
new file mode 100644
index 0000000..a9daf30
--- /dev/null
+++ b/test/InputFilter/AbstractInputVisitorTest.php
@@ -0,0 +1,146 @@
+getFilterChain()->attach(new ToInt());
+ $visitor = new MockAbstractInputVisitor([new ToIntVisitor()], []);
+
+ $actual = $visitor->visit($input);
+ self::assertEquals($expected, $actual);
+ }
+
+ public function testVisitSkipsCallableFilters(): void
+ {
+ $expected = new Union([new TNull(), new TString()]);
+ $filter = static fn (): never => self::fail("Should not be called");
+ $input = new Input('foo');
+ $input->getFilterChain()->attach($filter);
+ $visitor = new MockAbstractInputVisitor([new ToIntVisitor()], []);
+
+ $actual = $visitor->visit($input);
+ self::assertEquals($expected, $actual);
+ }
+
+ public function testVisitCallsValidator(): void
+ {
+ $expected = new Union([new TNumericString()]);
+ $input = new Input('foo');
+ $input->getValidatorChain()->attach(new Digits());
+ $visitor = new MockAbstractInputVisitor([], [new DigitsVisitor()]);
+
+ $actual = $visitor->visit($input);
+ self::assertEquals($expected, $actual);
+ }
+
+ /**
+ * @param non-empty-array $expected
+ */
+ #[DataProvider('addNotEmptyProvider')]
+ public function testVisitAddsNotEmptyValidator(
+ bool $continueIfEmpty,
+ bool $allowEmpty,
+ bool $required,
+ array $expected
+ ): void {
+ $expected = new Union($expected);
+ $input = new Input('foo');
+ $input->setContinueIfEmpty($continueIfEmpty);
+ $input->setAllowEmpty($allowEmpty);
+ $input->setRequired($required);
+ $visitor = new MockAbstractInputVisitor([], [new NotEmptyVisitor()]);
+
+ $actual = $visitor->visit($input);
+
+ self::assertEquals($expected, $actual);
+ }
+
+ public static function addNotEmptyProvider(): array
+ {
+ ConfigLoader::load();
+
+ // phpcs:disable Generic.Files.LineLength.TooLong
+ return [
+ "continue, allow, required" => [true, true, true, [new TString(), new TNull()]],
+ "continue, allow, not required" => [true, true, false, [new TString(), new TNull()]],
+ "continue, don't allow, required" => [true, false, true, [new TString(), new TNull()]],
+ "continue, don't allow, not required" => [true, false, false, [new TString(), new TNull()]],
+ "don't continue, allow, required" => [false, true, true, [new TNull(), new TString()]],
+ "don't continue, allow, not required" => [false, true, false, [new TNull(), new TString()]],
+ "don't continue, don't allow, required" => [false, false, true, [new TNonEmptyString()]],
+ "don't continue, don't allow, not required" => [false, false, false, [new TNull(), new TString()]],
+ ];
+ // phpcs:enable
+ }
+
+ public function testVisitInputDoesNotPrependDuplicateNotEmptyVisitor(): void
+ {
+ ConfigLoader::load();
+
+ $input = new Input('foo');
+ $input->setContinueIfEmpty(false);
+ $input->setAllowEmpty(false);
+ $input->setRequired(true);
+ $input->getValidatorChain()->attach(new NotEmpty());
+
+ $mockVisitor = self::createMock(ValidatorVisitorInterface::class);
+ $mockVisitor->method('visit')
+ ->willReturnCallback(static function (ValidatorInterface $validator, Union $previous) {
+ self::assertNotEquals(new Union([new TNonEmptyString()]), $previous);
+ return $previous;
+ });
+
+ $visitor = new MockAbstractInputVisitor([], [$mockVisitor, new NotEmptyVisitor()]);
+ $actual = $visitor->visit($input);
+ self::assertNotNull($actual);
+ self::assertEquals(new TNonEmptyString(), $actual->getSingleAtomic());
+ }
+
+ public function testVisitAllowEmptyReplacesNotEmptyString(): void
+ {
+ $expected = new Union([new TString(), new TNull()]);
+ $validatorVisitor = $this->createMock(ValidatorVisitorInterface::class);
+ $validatorVisitor->expects(self::once())
+ ->method('visit')
+ ->willReturn(new Union([new TNonEmptyString()]));
+ $input = new Input('foo');
+ $input->setRequired(false);
+ $input->setAllowEmpty(true);
+ $input->getValidatorChain()->attach($this->createStub(ValidatorInterface::class));
+ $visitor = new MockAbstractInputVisitor([], [$validatorVisitor]);
+
+ $actual = $visitor->visit($input);
+ self::assertEquals($expected, $actual);
+ }
+}
diff --git a/test/InputFilter/ArrayInputVisitorFactoryTest.php b/test/InputFilter/ArrayInputVisitorFactoryTest.php
index 6f6a7cf..887c6a3 100644
--- a/test/InputFilter/ArrayInputVisitorFactoryTest.php
+++ b/test/InputFilter/ArrayInputVisitorFactoryTest.php
@@ -4,12 +4,15 @@
namespace KynxTest\Laminas\FormShape\InputFilter;
+use Kynx\Laminas\FormShape\Filter\ToIntVisitor;
use Kynx\Laminas\FormShape\InputFilter\ArrayInputVisitorFactory;
-use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
+use Kynx\Laminas\FormShape\Validator\DigitsVisitor;
+use Laminas\Filter\ToInt;
use Laminas\InputFilter\ArrayInput;
+use Laminas\Validator\Digits;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
-use Psalm\Type\Union;
+use Psalm\Type\Atomic\TNonEmptyArray;
use Psr\Container\ContainerInterface;
#[CoversClass(ArrayInputVisitorFactory::class)]
@@ -20,13 +23,29 @@ public function testInvokeReturnsConfiguredInstance(): void
$container = $this->createStub(ContainerInterface::class);
$container->method('get')
->willReturnMap([
- [InputVisitor::class, new InputVisitor([], [])],
+ ['config', $this->getConfig([ToIntVisitor::class], [DigitsVisitor::class])],
+ [ToIntVisitor::class, new ToIntVisitor()],
+ [DigitsVisitor::class, new DigitsVisitor()],
]);
$factory = new ArrayInputVisitorFactory();
$instance = $factory($container);
+ $input = new ArrayInput();
+ $input->getFilterChain()->attach(new ToInt());
+ $input->getValidatorChain()->attach(new Digits());
$actual = $instance->visit(new ArrayInput());
- self::assertInstanceOf(Union::class, $actual);
+ self::assertNotNull($actual);
+ self::assertInstanceOf(TNonEmptyArray::class, $actual->getSingleAtomic());
+ }
+
+ private function getConfig(array $filterVisitors, array $validatorVisitors): array
+ {
+ return [
+ 'laminas-form-shape' => [
+ 'filter-visitors' => $filterVisitors,
+ 'validator-visitors' => $validatorVisitors,
+ ],
+ ];
}
}
diff --git a/test/InputFilter/ArrayInputVisitorTest.php b/test/InputFilter/ArrayInputVisitorTest.php
index 411bb13..5c542a8 100644
--- a/test/InputFilter/ArrayInputVisitorTest.php
+++ b/test/InputFilter/ArrayInputVisitorTest.php
@@ -4,43 +4,114 @@
namespace KynxTest\Laminas\FormShape\InputFilter;
+use Kynx\Laminas\FormShape\Decorator\PrettyPrinter;
+use Kynx\Laminas\FormShape\Filter\BooleanVisitor;
+use Kynx\Laminas\FormShape\Filter\ToIntVisitor;
use Kynx\Laminas\FormShape\InputFilter\ArrayInputVisitor;
-use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
+use Kynx\Laminas\FormShape\InputFilter\InputVisitorException;
+use Kynx\Laminas\FormShape\Psalm\ConfigLoader;
+use Kynx\Laminas\FormShape\Psalm\TypeUtil;
+use Kynx\Laminas\FormShape\Validator\DigitsVisitor;
+use Kynx\Laminas\FormShape\Validator\InArrayVisitor;
+use Laminas\Filter\Boolean;
+use Laminas\Filter\ToInt;
use Laminas\InputFilter\ArrayInput;
use Laminas\InputFilter\Input;
+use Laminas\Validator\Digits;
+use Laminas\Validator\InArray;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Psalm\Type;
+use Psalm\Type\Atomic\TArray;
+use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TNonEmptyArray;
-use Psalm\Type\Atomic\TNull;
-use Psalm\Type\Atomic\TString;
+use Psalm\Type\Atomic\TNumericString;
use Psalm\Type\Union;
#[CoversClass(ArrayInputVisitor::class)]
final class ArrayInputVisitorTest extends TestCase
{
- private ArrayInputVisitor $visitor;
+ public function testVisitNonArrayInputReturnsNull(): void
+ {
+ $visitor = new ArrayInputVisitor([], []);
+ $actual = $visitor->visit(new Input());
+ self::assertNull($actual);
+ }
- protected function setUp(): void
+ public function testVisitImpossibleInputThrowsException(): void
{
- $this->visitor = new ArrayInputVisitor(new InputVisitor([], []));
+ $input = new ArrayInput('foo');
+ $input->getFilterChain()->attach(new Boolean());
+ $input->getValidatorChain()->attach(new Digits());
+ $visitor = new ArrayInputVisitor([new BooleanVisitor()], [new DigitsVisitor()]);
+
+ self::expectException(InputVisitorException::class);
+ self::expectExceptionMessage("Cannot get type for 'foo'");
+ $visitor->visit($input);
}
- public function testVisitInvalidInputReturnsNull(): void
+ public function testVisitReturnsArrayOfValidatedType(): void
{
- $actual = $this->visitor->visit(new Input());
- self::assertNull($actual);
+ $expected = new Union([
+ new TArray([
+ Type::getArrayKey(),
+ new Union([new TNumericString(), new TInt()]),
+ ]),
+ ]);
+ $input = new ArrayInput();
+ $input->setRequired(false);
+ $input->setContinueIfEmpty(true);
+ $input->getFilterChain()->attach(new ToInt());
+ $input->getValidatorChain()->attach(new Digits());
+ $visitor = new ArrayInputVisitor([new ToIntVisitor()], [new DigitsVisitor()]);
+
+ $actual = $visitor->visit($input);
+ self::assertEquals($expected, $actual);
}
public function testVisitReturnsNonEmptyArray(): void
{
$expected = new Union([
- new TNonEmptyArray([Type::getArrayKey(), new Union([new TString(), new TNull()])]),
+ new TNonEmptyArray([
+ Type::getArrayKey(),
+ new Union([new TNumericString(), new TInt()]),
+ ]),
]);
$input = new ArrayInput();
- $input->setRequired(true);
+ $input->getFilterChain()->attach(new ToInt());
+ $input->getValidatorChain()->attach(new Digits());
+ $visitor = new ArrayInputVisitor([new ToIntVisitor()], [new DigitsVisitor()]);
- $actual = $this->visitor->visit($input);
+ $actual = $visitor->visit($input);
self::assertEquals($expected, $actual);
}
+
+ public function testVisitAddsFallbackValue(): void
+ {
+ ConfigLoader::load();
+
+ $expected = new Union([
+ new TNonEmptyArray([
+ Type::getArrayKey(),
+ new Union([
+ TypeUtil::getAtomicStringFromLiteral('1'),
+ TypeUtil::getAtomicStringFromLiteral('2'),
+ TypeUtil::getAtomicStringFromLiteral('a'),
+ ]),
+ ]),
+ ]);
+
+ $input = new ArrayInput();
+ $input->getValidatorChain()->attach(new InArray(['haystack' => [1, 2]]));
+ $input->setFallbackValue(['a']);
+ $visitor = new ArrayInputVisitor([], [new InArrayVisitor()]);
+
+ $actual = $visitor->visit($input);
+
+ self::assertNotNull($actual);
+ self::assertEquals($expected, $actual);
+
+ $decorated = (new PrettyPrinter())->decorate($actual);
+ self::assertSame("non-empty-array", $decorated);
+ }
}
diff --git a/test/InputFilter/CollectionInputTest.php b/test/InputFilter/CollectionInputTest.php
index 4df9ca0..2bca3e5 100644
--- a/test/InputFilter/CollectionInputTest.php
+++ b/test/InputFilter/CollectionInputTest.php
@@ -5,51 +5,18 @@
namespace KynxTest\Laminas\FormShape\InputFilter;
use Kynx\Laminas\FormShape\InputFilter\CollectionInput;
-use Laminas\Filter\FilterChain;
-use Laminas\Filter\ToInt;
-use Laminas\InputFilter\Input;
-use Laminas\Validator\NotEmpty;
-use Laminas\Validator\ValidatorChain;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
#[CoversClass(CollectionInput::class)]
final class CollectionInputTest extends TestCase
{
- public function testFromInputSetsProperties(): void
+ public function testSetCount(): void
{
- $actual = CollectionInput::fromInput(new Input(), 42);
- self::assertSame(42, $actual->getCount());
- }
-
- public function testFromInputDelegatesProperties(): void
- {
- $input = new Input();
- $input->setName('foo')
- ->setRequired(true)
- ->setAllowEmpty(true)
- ->setContinueIfEmpty(true)
- ->setBreakOnFailure(true)
- ->setFilterChain((new FilterChain())->attach(new ToInt()))
- ->setValidatorChain((new ValidatorChain())->attach(new NotEmpty()));
-
- $actual = CollectionInput::fromInput($input, 0);
-
- self::assertSame($input->getName(), $actual->getName());
- self::assertSame($input->isRequired(), $actual->isRequired());
- self::assertSame($input->allowEmpty(), $actual->allowEmpty());
- self::assertSame($input->continueIfEmpty(), $actual->continueIfEmpty());
- self::assertSame($input->breakOnFailure(), $actual->breakOnFailure());
- self::assertSame($input->getFilterChain(), $actual->getFilterChain());
- self::assertSame($input->getValidatorChain(), $actual->getValidatorChain());
- }
-
- public function testIsValidDelegates(): void
- {
- $input = CollectionInput::fromInput((new Input())->setRequired(true), 0);
- $input->setValue([]);
-
- $actual = $input->isValid();
- self::assertFalse($actual);
+ $expected = 1;
+ $input = new CollectionInput();
+ $input->setCount($expected);
+ $actual = $input->getCount();
+ self::assertSame($expected, $actual);
}
}
diff --git a/test/InputFilter/CollectionInputVisitorFactoryTest.php b/test/InputFilter/CollectionInputVisitorFactoryTest.php
deleted file mode 100644
index 0fcd24c..0000000
--- a/test/InputFilter/CollectionInputVisitorFactoryTest.php
+++ /dev/null
@@ -1,36 +0,0 @@
-createStub(ContainerInterface::class);
- $container->method('get')
- ->willReturnMap([
- [InputVisitor::class, new InputVisitor([], [])],
- ]);
-
- $factory = new CollectionInputVisitorFactory();
- $instance = $factory($container);
- $input = CollectionInput::fromInput(new Input(), 0);
-
- $union = $instance->visit($input);
- self::assertNotNull($union);
- $actual = $union->getSingleAtomic();
- self::assertInstanceOf(TArray::class, $actual);
- }
-}
diff --git a/test/InputFilter/CollectionInputVisitorTest.php b/test/InputFilter/CollectionInputVisitorTest.php
deleted file mode 100644
index 584be82..0000000
--- a/test/InputFilter/CollectionInputVisitorTest.php
+++ /dev/null
@@ -1,57 +0,0 @@
-visitor = new CollectionInputVisitor(new InputVisitor([], []));
- }
-
- public function testVisitInvalidInputReturnsNull(): void
- {
- $actual = $this->visitor->visit(new Input());
- self::assertNull($actual);
- }
-
- public function testVisitReturnsNonEmptyArray(): void
- {
- $expected = new Union([
- new TNonEmptyArray([Type::getArrayKey(), new Union([new TString(), new TNull()])]),
- ]);
- $input = CollectionInput::fromInput(new Input(), 42);
-
- $actual = $this->visitor->visit($input);
- self::assertEquals($expected, $actual);
- }
-
- public function testVisitReturnsArray(): void
- {
- $expected = new Union([
- new TArray([Type::getArrayKey(), new Union([new TString(), new TNull()])]),
- ]);
- $input = CollectionInput::fromInput(new Input(), 0);
-
- $actual = $this->visitor->visit($input);
- self::assertEquals($expected, $actual);
- }
-}
diff --git a/test/InputFilter/InputFilterVisitorFactoryTest.php b/test/InputFilter/InputFilterVisitorFactoryTest.php
index c0bc97f..8dddb76 100644
--- a/test/InputFilter/InputFilterVisitorFactoryTest.php
+++ b/test/InputFilter/InputFilterVisitorFactoryTest.php
@@ -50,14 +50,13 @@ public function testInvokeReturnsConfiguredInstance(): void
public function testInvokeSortsInputVisitors(): void
{
- $config = $this->getConfig([InputVisitor::class, ArrayInputVisitor::class]);
- $container = self::createStub(ContainerInterface::class);
- $inputVisitor = new InputVisitor([], []);
+ $config = $this->getConfig([InputVisitor::class, ArrayInputVisitor::class]);
+ $container = self::createStub(ContainerInterface::class);
$container->method('get')
->willReturnMap([
['config', $config],
[InputVisitor::class, new InputVisitor([], [])],
- [ArrayInputVisitor::class, new ArrayInputVisitor($inputVisitor)],
+ [ArrayInputVisitor::class, new ArrayInputVisitor([], [])],
]);
$factory = new InputFilterVisitorFactory();
diff --git a/test/InputFilter/InputFilterVisitorTest.php b/test/InputFilter/InputFilterVisitorTest.php
index a92eb6a..45b0fd4 100644
--- a/test/InputFilter/InputFilterVisitorTest.php
+++ b/test/InputFilter/InputFilterVisitorTest.php
@@ -308,7 +308,7 @@ public function testVisitReturnsDeeplyNestedImportType(): void
public function testVisitNoValidInputVisitorThrowsException(): void
{
$expected = "No input visitor configured for '" . Input::class . "'";
- $arrayVisitor = new ArrayInputVisitor(new InputVisitor([], []));
+ $arrayVisitor = new ArrayInputVisitor([], []);
$visitor = new InputFilterVisitor([$arrayVisitor]);
$inputFilter = new InputFilter();
$inputFilter->add(new Input());
diff --git a/test/InputFilter/InputVisitorFactoryTest.php b/test/InputFilter/InputVisitorFactoryTest.php
index 006f691..6ac8515 100644
--- a/test/InputFilter/InputVisitorFactoryTest.php
+++ b/test/InputFilter/InputVisitorFactoryTest.php
@@ -7,17 +7,13 @@
use Kynx\Laminas\FormShape\Filter\ToIntVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputVisitorFactory;
use Kynx\Laminas\FormShape\Validator\DigitsVisitor;
-use Kynx\Laminas\FormShape\ValidatorVisitorInterface;
use Laminas\Filter\ToInt;
use Laminas\InputFilter\Input;
use Laminas\Validator\Digits;
-use Laminas\Validator\ValidatorInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Psalm\Type\Atomic\TInt;
-use Psalm\Type\Atomic\TNull;
use Psalm\Type\Atomic\TNumericString;
-use Psalm\Type\Atomic\TString;
use Psalm\Type\Union;
use Psr\Container\ContainerInterface;
@@ -43,34 +39,6 @@ public function testInvokeReturnsConfiguredInstance(): void
self::assertEquals($expected, $actual);
}
- public function testInvokeGetsVisitorFromContainer(): void
- {
- $config = $this->getConfig([], [ValidatorVisitorInterface::class]);
- $validatorVisitor = $this->createMock(ValidatorVisitorInterface::class);
- $container = self::createStub(ContainerInterface::class);
- $container->method('has')
- ->willReturn(true);
- $container->method('get')
- ->willReturnMap([
- ['config', $config],
- [ValidatorVisitorInterface::class, $validatorVisitor],
- ]);
-
- $factory = new InputVisitorFactory();
- $instance = $factory($container);
-
- $expected = new Union([new TInt(), new TString(), new TNull()]);
- $input = new Input('foo');
- $input->setRequired(false); // so we don't attach NotEmpty
- $input->getValidatorChain()->attach($this->createStub(ValidatorInterface::class));
-
- $validatorVisitor->expects(self::once())
- ->method('visit')
- ->willReturn(new Union([new TInt()]));
- $actual = $instance->visit($input);
- self::assertEquals($expected, $actual);
- }
-
private function getConfig(array $filterVisitors, array $validatorVisitors): array
{
return [
diff --git a/test/InputFilter/InputVisitorTest.php b/test/InputFilter/InputVisitorTest.php
index 985f088..505546d 100644
--- a/test/InputFilter/InputVisitorTest.php
+++ b/test/InputFilter/InputVisitorTest.php
@@ -5,124 +5,20 @@
namespace KynxTest\Laminas\FormShape\InputFilter;
use Kynx\Laminas\FormShape\Filter\BooleanVisitor;
-use Kynx\Laminas\FormShape\Filter\ToIntVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputVisitor;
use Kynx\Laminas\FormShape\InputFilter\InputVisitorException;
-use Kynx\Laminas\FormShape\Psalm\ConfigLoader;
use Kynx\Laminas\FormShape\Validator\DigitsVisitor;
-use Kynx\Laminas\FormShape\Validator\NotEmptyVisitor;
-use Kynx\Laminas\FormShape\ValidatorVisitorInterface;
use Laminas\Filter\Boolean;
-use Laminas\Filter\ToInt;
use Laminas\InputFilter\Input;
use Laminas\Validator\Digits;
-use Laminas\Validator\ValidatorInterface;
use PHPUnit\Framework\Attributes\CoversClass;
-use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
-use Psalm\Type\Atomic;
-use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralFloat;
-use Psalm\Type\Atomic\TNonEmptyString;
-use Psalm\Type\Atomic\TNull;
-use Psalm\Type\Atomic\TNumericString;
-use Psalm\Type\Atomic\TString;
use Psalm\Type\Union;
#[CoversClass(InputVisitor::class)]
final class InputVisitorTest extends TestCase
{
- public function testVisitCallsFilter(): void
- {
- $expected = new Union([new TNull(), new TString(), new TInt()]);
- $input = new Input('foo');
- $input->getFilterChain()->attach(new ToInt());
- $visitor = new InputVisitor([new ToIntVisitor()], []);
-
- $actual = $visitor->visit($input);
- self::assertEquals($expected, $actual);
- }
-
- public function testVisitSkipsCallableFilters(): void
- {
- $expected = new Union([new TNull(), new TString()]);
- $filter = static fn (): never => self::fail("Should not be called");
- $input = new Input('foo');
- $input->getFilterChain()->attach($filter);
- $visitor = new InputVisitor([new ToIntVisitor()], []);
-
- $actual = $visitor->visit($input);
- self::assertEquals($expected, $actual);
- }
-
- public function testVisitCallsValidator(): void
- {
- $expected = new Union([new TNumericString()]);
- $input = new Input('foo');
- $input->getValidatorChain()->attach(new Digits());
- $visitor = new InputVisitor([], [new DigitsVisitor()]);
-
- $actual = $visitor->visit($input);
- self::assertEquals($expected, $actual);
- }
-
- /**
- * @param non-empty-array $expected
- */
- #[DataProvider('addNotEmptyProvider')]
- public function testVisitAddsNotEmptyValidator(
- bool $continueIfEmpty,
- bool $allowEmpty,
- bool $required,
- array $expected
- ): void {
- $expected = new Union($expected);
- $input = new Input('foo');
- $input->setContinueIfEmpty($continueIfEmpty);
- $input->setAllowEmpty($allowEmpty);
- $input->setRequired($required);
- $visitor = new InputVisitor([], [new NotEmptyVisitor()]);
-
- $actual = $visitor->visit($input);
-
- self::assertEquals($expected, $actual);
- }
-
- public static function addNotEmptyProvider(): array
- {
- ConfigLoader::load();
-
- // phpcs:disable Generic.Files.LineLength.TooLong
- return [
- "continue, allow, required" => [true, true, true, [new TString(), new TNull()]],
- "continue, allow, not required" => [true, true, false, [new TString(), new TNull()]],
- "continue, don't allow, required" => [true, false, true, [new TString(), new TNull()]],
- "continue, don't allow, not required" => [true, false, false, [new TString(), new TNull()]],
- "don't continue, allow, required" => [false, true, true, [new TNull(), new TString()]],
- "don't continue, allow, not required" => [false, true, false, [new TNull(), new TString()]],
- "don't continue, don't allow, required" => [false, false, true, [new TNonEmptyString()]],
- "don't continue, don't allow, not required" => [false, false, false, [new TNull(), new TString()]],
- ];
- // phpcs:enable
- }
-
- public function testVisitAllowEmptyReplacesNotEmptyString(): void
- {
- $expected = new Union([new TString(), new TNull()]);
- $validatorVisitor = $this->createMock(ValidatorVisitorInterface::class);
- $validatorVisitor->expects(self::once())
- ->method('visit')
- ->willReturn(new Union([new TNonEmptyString()]));
- $input = new Input('foo');
- $input->setRequired(false);
- $input->setAllowEmpty(true);
- $input->getValidatorChain()->attach($this->createStub(ValidatorInterface::class));
- $visitor = new InputVisitor([], [$validatorVisitor]);
-
- $actual = $visitor->visit($input);
- self::assertEquals($expected, $actual);
- }
-
public function testVisitAddsFallback(): void
{
$expected = new Union([new TLiteralFloat(1.23)]);
diff --git a/test/InputFilter/MockAbstractInputVisitor.php b/test/InputFilter/MockAbstractInputVisitor.php
new file mode 100644
index 0000000..836a2bc
--- /dev/null
+++ b/test/InputFilter/MockAbstractInputVisitor.php
@@ -0,0 +1,19 @@
+visitInput($input, new Union([new TNull(), new TString()]));
+ }
+}
diff --git a/test/InputFilter/MockAbstractInputVisitorFactory.php b/test/InputFilter/MockAbstractInputVisitorFactory.php
new file mode 100644
index 0000000..67b8530
--- /dev/null
+++ b/test/InputFilter/MockAbstractInputVisitorFactory.php
@@ -0,0 +1,19 @@
+getFilterVisitors($container),
+ $this->getValidatorVisitors($container)
+ );
+ }
+}
diff --git a/test/InputFilter/MockInputVisitor.php b/test/InputFilter/MockInputVisitor.php
new file mode 100644
index 0000000..59ab082
--- /dev/null
+++ b/test/InputFilter/MockInputVisitor.php
@@ -0,0 +1,28 @@
+filterVisitors;
+ }
+
+ public function getValidatorVisitors(): array
+ {
+ return $this->validatorVisitors;
+ }
+
+ public function visit(InputInterface $input): ?Union
+ {
+ return TypeUtil::getEmptyUnion();
+ }
+}