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
3 changes: 3 additions & 0 deletions src/translation/lang/en/validation.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
'array' => 'The :attribute must have between :min and :max items.',
],
'boolean' => 'The :attribute field must be true or false.',
'can' => 'The :attribute field contains an unauthorized value.',
'confirmed' => 'The :attribute confirmation does not match.',
'contains' => 'The :attribute field is missing a required value.',
'date' => 'The :attribute is not a valid date.',
'date_equals' => 'The :attribute must be a date equal to :date.',
'date_format' => 'The :attribute does not match the format :format.',
Expand All @@ -45,6 +47,7 @@
'digits_between' => 'The :attribute must be between :min and :max digits.',
'dimensions' => 'The :attribute has invalid image dimensions.',
'distinct' => 'The :attribute field has a duplicate value.',
'doesnt_contain' => 'The :attribute field must not contain any of the following: :values.',
'doesnt_end_with' => 'The :attribute must not end with one of the following: :values.',
'doesnt_start_with' => 'The :attribute must not start with one of the following: :values.',
'email' => 'The :attribute must be a valid email address.',
Expand Down
20 changes: 20 additions & 0 deletions src/validation/src/Concerns/ValidatesAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,26 @@ public function validateContains(string $attribute, mixed $value, mixed $paramet
return true;
}

/**
* Validate an attribute does not contain a list of values.
*
* @param array<int, int|string> $parameters
*/
public function validateDoesntContain(string $attribute, mixed $value, mixed $parameters): bool
{
if (! is_array($value)) {
return false;
}

foreach ($parameters as $parameter) {
if (in_array($parameter, $value)) {
return false;
}
}

return true;
}

/**
* Validate that the password of the currently authenticated user matches the given value.
*
Expand Down
34 changes: 34 additions & 0 deletions src/validation/src/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
use Hypervel\Validation\Rules\AnyOf;
use Hypervel\Validation\Rules\ArrayRule;
use Hypervel\Validation\Rules\Can;
use Hypervel\Validation\Rules\Contains;
use Hypervel\Validation\Rules\Date;
use Hypervel\Validation\Rules\Dimensions;
use Hypervel\Validation\Rules\DoesntContain;
use Hypervel\Validation\Rules\Email;
use Hypervel\Validation\Rules\Enum;
use Hypervel\Validation\Rules\ExcludeIf;
Expand Down Expand Up @@ -121,6 +123,30 @@ public static function notIn(array|Arrayable|BackedEnum|string|UnitEnum $values)
return new NotIn(is_array($values) ? $values : func_get_args());
}

/**
* Get a contains rule builder instance.
*/
public static function contains(array|Arrayable|BackedEnum|string|UnitEnum $values): Contains
{
if ($values instanceof Arrayable) {
$values = $values->toArray();
}

return new Contains(is_array($values) ? $values : func_get_args());
}

/**
* Get a doesnt_contain rule builder instance.
*/
public static function doesntContain(array|Arrayable|BackedEnum|string|UnitEnum $values): DoesntContain
{
if ($values instanceof Arrayable) {
$values = $values->toArray();
}

return new DoesntContain(is_array($values) ? $values : func_get_args());
}

/**
* Get a required_if rule builder instance.
*/
Expand Down Expand Up @@ -153,6 +179,14 @@ public static function date(): Date
return new Date();
}

/**
* Get a datetime rule builder instance.
*/
public static function dateTime(): Date
{
return (new Date())->format('Y-m-d H:i:s');
}

/**
* Get an email rule builder instance.
*/
Expand Down
46 changes: 46 additions & 0 deletions src/validation/src/Rules/Contains.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Hypervel\Validation\Rules;

use BackedEnum;
use Hyperf\Contract\Arrayable;
use Stringable;
use UnitEnum;

use function Hypervel\Support\enum_value;

class Contains implements Stringable
{
/**
* The values that should be contained in the attribute.
*/
protected array $values;

/**
* Create a new contains rule instance.
*/
public function __construct(array|Arrayable|BackedEnum|string|UnitEnum $values)
{
if ($values instanceof Arrayable) {
$values = $values->toArray();
}

$this->values = is_array($values) ? $values : func_get_args();
}

/**
* Convert the rule to a validation string.
*/
public function __toString(): string
{
$values = array_map(function ($value) {
$value = enum_value($value);

return '"' . str_replace('"', '""', (string) $value) . '"';
}, $this->values);

return 'contains:' . implode(',', $values);
}
}
46 changes: 46 additions & 0 deletions src/validation/src/Rules/DoesntContain.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Hypervel\Validation\Rules;

use BackedEnum;
use Hyperf\Contract\Arrayable;
use Stringable;
use UnitEnum;

use function Hypervel\Support\enum_value;

class DoesntContain implements Stringable
{
/**
* The values that should not be contained in the attribute.
*/
protected array $values;

/**
* Create a new doesnt_contain rule instance.
*/
public function __construct(array|Arrayable|BackedEnum|string|UnitEnum $values)
{
if ($values instanceof Arrayable) {
$values = $values->toArray();
}

$this->values = is_array($values) ? $values : func_get_args();
}

/**
* Convert the rule to a validation string.
*/
public function __toString(): string
{
$values = array_map(function ($value) {
$value = enum_value($value);

return '"' . str_replace('"', '""', (string) $value) . '"';
}, $this->values);

return 'doesnt_contain:' . implode(',', $values);
}
}
98 changes: 98 additions & 0 deletions tests/Validation/ValidationContainsRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace Hypervel\Tests\Validation;

use Hypervel\Tests\Validation\fixtures\Values;
use Hypervel\Translation\ArrayLoader;
use Hypervel\Translation\Translator;
use Hypervel\Validation\Rule;
use Hypervel\Validation\Rules\Contains;
use Hypervel\Validation\Validator;
use PHPUnit\Framework\TestCase;

include_once 'Enums.php';

/**
* @internal
* @coversNothing
*/
class ValidationContainsRuleTest extends TestCase
{
public function testItCorrectlyFormatsAStringVersionOfTheRule()
{
$rule = new Contains(['foo', 'bar']);

$this->assertSame('contains:"foo","bar"', (string) $rule);

$rule = new Contains(collect(['foo', 'bar']));

$this->assertSame('contains:"foo","bar"', (string) $rule);

$rule = new Contains(['value with "quotes"']);

$this->assertSame('contains:"value with ""quotes"""', (string) $rule);

$rule = Rule::contains(['foo', 'bar']);

$this->assertSame('contains:"foo","bar"', (string) $rule);

$rule = Rule::contains(collect([1, 2, 3]));

$this->assertSame('contains:"1","2","3"', (string) $rule);

$rule = Rule::contains(new Values());

$this->assertSame('contains:"1","2","3","4"', (string) $rule);

$rule = Rule::contains('foo', 'bar', 'baz');

$this->assertSame('contains:"foo","bar","baz"', (string) $rule);

$rule = new Contains('foo', 'bar', 'baz');

$this->assertSame('contains:"foo","bar","baz"', (string) $rule);

$rule = Rule::contains([StringStatus::done]);

$this->assertSame('contains:"done"', (string) $rule);

$rule = Rule::contains([IntegerStatus::done]);

$this->assertSame('contains:"2"', (string) $rule);

$rule = Rule::contains([PureEnum::one]);

$this->assertSame('contains:"one"', (string) $rule);
}

public function testContainsRuleValidation()
{
$trans = new Translator(new ArrayLoader(), 'en');

// Array contains the required value
$v = new Validator($trans, ['x' => ['foo', 'bar', 'baz']], ['x' => Rule::contains('foo')]);
$this->assertTrue($v->passes());

// Array contains multiple required values
$v = new Validator($trans, ['x' => ['foo', 'bar', 'baz']], ['x' => Rule::contains('foo', 'bar')]);
$this->assertTrue($v->passes());

// Array missing a required value
$v = new Validator($trans, ['x' => ['foo', 'bar']], ['x' => Rule::contains('baz')]);
$this->assertFalse($v->passes());

// Array missing one of multiple required values
$v = new Validator($trans, ['x' => ['foo', 'bar']], ['x' => Rule::contains('foo', 'qux')]);
$this->assertFalse($v->passes());

// Non-array value fails
$v = new Validator($trans, ['x' => 'foo'], ['x' => Rule::contains('foo')]);
$this->assertFalse($v->passes());

// Combined with other rules
$v = new Validator($trans, ['x' => ['foo', 'bar']], ['x' => ['required', 'array', Rule::contains('foo')]]);
$this->assertTrue($v->passes());
}
}
98 changes: 98 additions & 0 deletions tests/Validation/ValidationDoesntContainRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace Hypervel\Tests\Validation;

use Hypervel\Tests\Validation\fixtures\Values;
use Hypervel\Translation\ArrayLoader;
use Hypervel\Translation\Translator;
use Hypervel\Validation\Rule;
use Hypervel\Validation\Rules\DoesntContain;
use Hypervel\Validation\Validator;
use PHPUnit\Framework\TestCase;

include_once 'Enums.php';

/**
* @internal
* @coversNothing
*/
class ValidationDoesntContainRuleTest extends TestCase
{
public function testItCorrectlyFormatsAStringVersionOfTheRule()
{
$rule = new DoesntContain(['foo', 'bar']);

$this->assertSame('doesnt_contain:"foo","bar"', (string) $rule);

$rule = new DoesntContain(collect(['foo', 'bar']));

$this->assertSame('doesnt_contain:"foo","bar"', (string) $rule);

$rule = new DoesntContain(['value with "quotes"']);

$this->assertSame('doesnt_contain:"value with ""quotes"""', (string) $rule);

$rule = Rule::doesntContain(['foo', 'bar']);

$this->assertSame('doesnt_contain:"foo","bar"', (string) $rule);

$rule = Rule::doesntContain(collect([1, 2, 3]));

$this->assertSame('doesnt_contain:"1","2","3"', (string) $rule);

$rule = Rule::doesntContain(new Values());

$this->assertSame('doesnt_contain:"1","2","3","4"', (string) $rule);

$rule = Rule::doesntContain('foo', 'bar', 'baz');

$this->assertSame('doesnt_contain:"foo","bar","baz"', (string) $rule);

$rule = new DoesntContain('foo', 'bar', 'baz');

$this->assertSame('doesnt_contain:"foo","bar","baz"', (string) $rule);

$rule = Rule::doesntContain([StringStatus::done]);

$this->assertSame('doesnt_contain:"done"', (string) $rule);

$rule = Rule::doesntContain([IntegerStatus::done]);

$this->assertSame('doesnt_contain:"2"', (string) $rule);

$rule = Rule::doesntContain([PureEnum::one]);

$this->assertSame('doesnt_contain:"one"', (string) $rule);
}

public function testDoesntContainRuleValidation()
{
$trans = new Translator(new ArrayLoader(), 'en');

// Array doesn't contain the forbidden value
$v = new Validator($trans, ['x' => ['foo', 'bar', 'baz']], ['x' => Rule::doesntContain('qux')]);
$this->assertTrue($v->passes());

// Array doesn't contain any of the forbidden values
$v = new Validator($trans, ['x' => ['foo', 'bar', 'baz']], ['x' => Rule::doesntContain('qux', 'quux')]);
$this->assertTrue($v->passes());

// Array contains a forbidden value
$v = new Validator($trans, ['x' => ['foo', 'bar', 'baz']], ['x' => Rule::doesntContain('foo')]);
$this->assertFalse($v->passes());

// Array contains one of the forbidden values
$v = new Validator($trans, ['x' => ['foo', 'bar', 'baz']], ['x' => Rule::doesntContain('qux', 'bar')]);
$this->assertFalse($v->passes());

// Non-array value fails
$v = new Validator($trans, ['x' => 'foo'], ['x' => Rule::doesntContain('foo')]);
$this->assertFalse($v->passes());

// Combined with other rules
$v = new Validator($trans, ['x' => ['foo', 'bar']], ['x' => ['required', 'array', Rule::doesntContain('baz')]]);
$this->assertTrue($v->passes());
}
}