Skip to content

Commit

Permalink
Add support for isMultiple validator. (#21)
Browse files Browse the repository at this point in the history
* Add support for `isMultiple` validator.

* Fix typo in `README.md`

---------

Co-authored-by: david_smith <[email protected]>
  • Loading branch information
zero-to-prod and david_smith authored Nov 19, 2024
1 parent a3b2898 commit ecab046
Show file tree
Hide file tree
Showing 16 changed files with 296 additions and 7 deletions.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class DataModelHelper
- [pregMatch](#pregmatch): Perform a regular expression match.
- [isUrl](#isurl): Validates a url.
- [isEmail](#isemail): Validates an email.
- [isMultiple](#ismultiple): Validate a value is a multiple of another.

### `mapOf`

Expand Down Expand Up @@ -486,7 +487,7 @@ echo $User->name; // Outputs: 's'

### `isUrl`

Use `isUrl` to validate a url.
Use `isUrl` to validate an url.

```php
class User
Expand Down Expand Up @@ -523,4 +524,25 @@ class User
])]
public string $url;
}
```

### `isMultiple`

Use `isMultiple` to validate a value is a multiple of another.

```php
class User
{
use \Zerotoprod\DataModel\DataModel;
use \Zerotoprod\DataModelHelper\DataModelHelper;

#[Describe([
'cast' => [self::class, 'isMultiple'],
'of' => 2 // The number the value is a multiple of
'on_fail' => [MyAction::class, 'method'], // Optional. Invoked when validation fails.
'exception' => MyException::class, // Optional. Throws an exception when not a valid email.
'required', // Throws PropertyRequiredException when value not present.
])]
public string $url;
}
```
55 changes: 51 additions & 4 deletions src/DataModelHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public static function mapOf(mixed $value, array $context, ?ReflectionAttribute
* 'cast' => [self::class, 'pregReplace'],
* 'pattern' => '/s/', // any regular expression
* 'replacement' => '', // default
* 'required', // Throws PropertyRequiredException when value not present
* 'required', // Throws PropertyRequiredException when value not present.
* ])]
* ```
*/
Expand Down Expand Up @@ -143,7 +143,7 @@ public static function pregReplace(mixed $value, array $context, ?ReflectionAttr
* 'match_on' => 0 // Index of the $matches to return
* 'flags' => PREG_UNMATCHED_AS_NULL
* 'offset' => 0,
* 'required', // Throws PropertyRequiredException when value not present
* 'required', // Throws PropertyRequiredException when value not present.
* ])]
* ```
*/
Expand Down Expand Up @@ -183,7 +183,7 @@ public static function pregMatch(mixed $value, array $context, ?ReflectionAttrib
* 'protocols' => ['http', 'udp'], // Optional. Defaults to all.
* 'on_fail' => [MyAction::class, 'method'], // Optional. Invoked when validation fails.
* 'exception' => MyException::class, // Optional. Throws an exception when not url.
* 'required', // Throws PropertyRequiredException when value not present
* 'required', // Throws PropertyRequiredException when value not present.
* ])]
* ```
*/
Expand Down Expand Up @@ -229,7 +229,7 @@ public static function isUrl(mixed $value, array $context, ?ReflectionAttribute
* 'cast' => [self::class, 'isEmail'],
* 'on_fail' => [MyAction::class, 'method'], // Optional. Invoked when validation fails.
* 'exception' => MyException::class, // Optional. Throws an exception when not a valid email.
* 'required', // Throws PropertyRequiredException when value not present
* 'required', // Throws PropertyRequiredException when value not present.
* ])]
* ```
*/
Expand Down Expand Up @@ -267,4 +267,51 @@ public static function isEmail(mixed $value, array $context, ?ReflectionAttribut

return $value;
}

/**
* Determine if a given value is a valid URL.
* ```
* #[Describe([
* 'cast' => [self::class, 'isMultiple'],
* 'of' => 2 // The number the value is a multiple of
* 'on_fail' => [MyAction::class, 'method'], // Optional. Invoked when validation fails.
* 'exception' => MyException::class, // Optional. Throws an exception when not a valid email.
* 'required', // Throws PropertyRequiredException when value not present.
* ])]
* ```
*/
public static function isMultiple(mixed $value, array $context, ?ReflectionAttribute $Attribute, ReflectionProperty $Property): ?string
{
$args = $Attribute?->getArguments()[0];
if ((!empty($args['required']) || in_array('required', $args, true))
&& !isset($context[$Property->getName()])
) {
throw new PropertyRequiredException("Property `\${$Property->getName()}` is required.");
}

if (!$value && $Property->getType()?->allowsNull()) {
return null;
}

if (!is_string($value)) {
if (isset($args['on_fail'])) {
call_user_func($args['on_fail'], $value, $context, $Attribute, $Property);
}

if (isset($args['exception'])) {
throw new $args['exception'];
}
}

if ((int)$value % $args['of'] !== 0) {
if (isset($args['on_fail'])) {
call_user_func($args['on_fail'], $value, $context, $Attribute, $Property);
}
if (isset($args['exception'])) {
throw new $args['exception'];
}
}

return $value;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Tests\Unit\IsEmail\ValidUrl;
namespace Tests\Unit\IsEmail\ValidEmail;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Tests\Unit\IsEmail\ValidUrl;
namespace Tests\Unit\IsEmail\ValidEmail;

use Zerotoprod\DataModel\DataModel;
use Zerotoprod\DataModel\Describe;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Tests\Unit\IsMultiple\Callable\InvalidEmail;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class InvalidMultipleTest extends TestCase
{
#[Test] public function invalid_email(): void
{
$this->expectException(NotMultipleException::class);

User::from([
User::value => 2,
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Tests\Unit\IsMultiple\Callable\InvalidEmail;

use RuntimeException;

class NotMultipleException extends RuntimeException
{

}
27 changes: 27 additions & 0 deletions tests/Unit/IsMultiple/Callable/InvalidEmail/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Tests\Unit\IsMultiple\Callable\InvalidEmail;

use Zerotoprod\DataModel\DataModel;
use Zerotoprod\DataModel\Describe;
use Zerotoprod\DataModelHelper\DataModelHelper;

class User
{
use DataModel;
use DataModelHelper;

public const value = 'value';

#[Describe([
'cast' => [self::class, 'isMultiple'],
'of' => 7,
'on_fail' => [self::class, 'failed'],
])]
public int $value;

public static function failed(): string
{
throw new NotMultipleException();
}
}
17 changes: 17 additions & 0 deletions tests/Unit/IsMultiple/Exception/InvalidUrl/CustomExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Tests\Unit\IsMultiple\Exception\InvalidUrl;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class CustomExceptionTest extends TestCase
{
#[Test] public function invalid_email(): void
{
$this->expectException(NotMultipleException::class);
User::from([
User::value => 2,
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Tests\Unit\IsMultiple\Exception\InvalidUrl;

use RuntimeException;

class NotMultipleException extends RuntimeException
{

}
22 changes: 22 additions & 0 deletions tests/Unit/IsMultiple/Exception/InvalidUrl/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Tests\Unit\IsMultiple\Exception\InvalidUrl;

use Zerotoprod\DataModel\DataModel;
use Zerotoprod\DataModel\Describe;
use Zerotoprod\DataModelHelper\DataModelHelper;

class User
{
use DataModel;
use DataModelHelper;

public const value = 'value';

#[Describe([
'cast' => [self::class, 'isMultiple'],
'of' => 3,
'exception' => NotMultipleException::class
])]
public int $value;
}
21 changes: 21 additions & 0 deletions tests/Unit/IsMultiple/Nullable/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Tests\Unit\IsMultiple\Nullable;

use Zerotoprod\DataModel\DataModel;
use Zerotoprod\DataModel\Describe;
use Zerotoprod\DataModelHelper\DataModelHelper;

class User
{
use DataModel;
use DataModelHelper;

public const value = 'value';

#[Describe([
'cast' => [self::class, 'isMultiple'],
'of' => 10
])]
public ?int $value;
}
18 changes: 18 additions & 0 deletions tests/Unit/IsMultiple/Nullable/ValidMultipleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Tests\Unit\IsMultiple\Nullable;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class ValidMultipleTest extends TestCase
{
#[Test] public function valid(): void
{
$User = User::from([
User::value => null,
]);

self::assertNull($User->value);
}
}
17 changes: 17 additions & 0 deletions tests/Unit/IsMultiple/Required/RequiredTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Tests\Unit\IsMultiple\Required;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
use Zerotoprod\DataModel\PropertyRequiredException;

class RequiredTest extends TestCase
{
#[Test] public function required(): void
{
$this->expectException(PropertyRequiredException::class);
$this->expectExceptionMessage('Property `$value` is required.');
User::from();
}
}
21 changes: 21 additions & 0 deletions tests/Unit/IsMultiple/Required/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Tests\Unit\IsMultiple\Required;

use Zerotoprod\DataModel\DataModel;
use Zerotoprod\DataModel\Describe;
use Zerotoprod\DataModelHelper\DataModelHelper;

class User
{
use DataModel;
use DataModelHelper;

public const value = 'value';

#[Describe([
'cast' => [self::class, 'isMultiple'],
'required'
])]
public string $value;
}
21 changes: 21 additions & 0 deletions tests/Unit/IsMultiple/ValidMultiple/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Tests\Unit\IsMultiple\ValidMultiple;

use Zerotoprod\DataModel\DataModel;
use Zerotoprod\DataModel\Describe;
use Zerotoprod\DataModelHelper\DataModelHelper;

class User
{
use DataModel;
use DataModelHelper;

public const value = 'value';

#[Describe([
'cast' => [self::class, 'isMultiple'],
'of' => 10
])]
public int $value;
}
18 changes: 18 additions & 0 deletions tests/Unit/IsMultiple/ValidMultiple/ValidMultipleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Tests\Unit\IsMultiple\ValidMultiple;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class ValidMultipleTest extends TestCase
{
#[Test] public function valid(): void
{
$User = User::from([
User::value => 100,
]);

self::assertEquals(100, $User->value);
}
}

0 comments on commit ecab046

Please sign in to comment.