Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add handling for enum as haystack #168

Open
wants to merge 3 commits into
base: 2.30.x
Choose a base branch
from
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
68 changes: 66 additions & 2 deletions src/InArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

namespace Laminas\Validator;

use BackedEnum;
use RecursiveArrayIterator;
use RecursiveIteratorIterator;
use UnitEnum;

use function array_map;
use function in_array;
use function is_bool;
use function is_float;
use function is_int;
use function is_string;
use function is_subclass_of;

class InArray extends AbstractValidator
{
Expand Down Expand Up @@ -46,6 +50,13 @@ class InArray extends AbstractValidator
*/
protected $haystack;

/**
* Enum of possible values
*
* @var class-string<UnitEnum>|null
*/
protected $enum;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this private before it spreads :D


/**
* Type of strict check to be used. Due to "foo" == 0 === TRUE with in_array when strict = false,
* an option has been added to prevent this. When $strict = 0/false, the most
Expand All @@ -66,7 +77,7 @@ class InArray extends AbstractValidator
/**
* Returns the haystack option
*
* @return mixed
* @return array
* @throws Exception\RuntimeException If haystack option is not set.
*/
public function getHaystack()
Expand All @@ -80,7 +91,7 @@ public function getHaystack()
/**
* Sets the haystack option
*
* @param mixed $haystack
* @param array $haystack
* @return $this Provides a fluent interface
*/
public function setHaystack(array $haystack)
Expand All @@ -89,6 +100,30 @@ public function setHaystack(array $haystack)
return $this;
}

/**
* @return class-string<UnitEnum>|null
*/
public function getEnum(): ?string
{
return $this->enum;
}

/**
* Set the enum option
*
* @param class-string<UnitEnum> $enum
* @return $this Provides a fluent interface
*/
public function setEnum(string $enum): self
{
if (! is_subclass_of($enum, UnitEnum::class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can skip this particular check: class-string<UnitEnum> already verifies this, and code written in newer codebases generally respects types in a much cleaner way

throw new Exception\RuntimeException('enum has invalid type');
}

$this->enum = $enum;
return $this;
}
Comment on lines +117 to +125
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this method should call setHaystack(): that would remove the need for touching the isValid() implementation


/**
* Returns the strict option
*
Expand Down Expand Up @@ -169,6 +204,35 @@ public function setRecursive($recursive)
*/
public function isValid($value)
{
$enum = $this->getEnum();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we make $this->enum private, we can directly access it here: no need for a method call

if (is_string($enum)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just check for !== null

if (is_subclass_of($enum, 'BackedEnum')) {
$enumHaystack = array_map(
static fn (BackedEnum $case) => $case->value,
$enum::cases()
);

if (in_array($value, $enumHaystack, (bool) $this->strict)) {
return true;
}

$this->error(self::NOT_IN_ARRAY);
return false;
}

$enumHaystack = array_map(
static fn (UnitEnum $case): string => $case->name,
$enum::cases()
);

if (in_array($value, $enumHaystack, (bool) $this->strict)) {
return true;
}

$this->error(self::NOT_IN_ARRAY);
return false;
}

// we create a copy of the haystack in case we need to modify it
$haystack = $this->getHaystack();

Expand Down
52 changes: 52 additions & 0 deletions test/InArrayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

use Laminas\Validator\Exception\RuntimeException;
use Laminas\Validator\InArray;
use LaminasTest\Validator\TestAsset\CustomTraversable;
use LaminasTest\Validator\TestAsset\Enum\TestBackedIntEnum;
use LaminasTest\Validator\TestAsset\Enum\TestBackedStringEnum;
use LaminasTest\Validator\TestAsset\Enum\TestUnitEnum;
use PHPUnit\Framework\TestCase;

use function array_keys;
Expand Down Expand Up @@ -472,4 +476,52 @@ public function testBooleanNotStrictEnforcesNonStrictMode(array $haystack, mixed
self::assertTrue($validator->isValid($valid));
self::assertFalse($validator->isValid($invalid));
}

/**
* @requires PHP 8.1
*/
public function testEnumValidation(): void
{
$validator = new InArray([
'enum' => TestUnitEnum::class,
]);

self::assertTrue($validator->isValid('foo'));
self::assertFalse($validator->isValid('baz'));

$validator = new InArray([
'enum' => TestBackedStringEnum::class,
]);

self::assertTrue($validator->isValid('foo'));
self::assertFalse($validator->isValid('baz'));

$validator = new InArray([
'enum' => TestBackedIntEnum::class,
]);

self::assertTrue($validator->isValid(1));
self::assertFalse($validator->isValid(3));

$validator = new InArray([
'enum' => TestBackedIntEnum::class,
'strict' => true,
]);

self::assertTrue($validator->isValid(1));
self::assertFalse($validator->isValid('2'));
}

/**
* @requires PHP 8.1
*/
public function testEnumOptionException(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('enum has invalid type');

$validator = new InArray([
'enum' => CustomTraversable::class,
]);
}
}
11 changes: 11 additions & 0 deletions test/TestAsset/Enum/TestBackedIntEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace LaminasTest\Validator\TestAsset\Enum;

enum TestBackedIntEnum: int
{
case Foo = 1;
case Bar = 2;
}
11 changes: 11 additions & 0 deletions test/TestAsset/Enum/TestBackedStringEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace LaminasTest\Validator\TestAsset\Enum;

enum TestBackedStringEnum: string
{
case Foo = 'foo';
case Bar = 'bar';
}
11 changes: 11 additions & 0 deletions test/TestAsset/Enum/TestUnitEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace LaminasTest\Validator\TestAsset\Enum;

enum TestUnitEnum
{
case foo;
case bar;
}