Skip to content

Commit

Permalink
feat/add-support-for-checking-emails (#19)
Browse files Browse the repository at this point in the history
* Add tests.

* Update `README.md`.

* Add support for validating emails.

* Add "zero-to-prod/validate-email" dep.

---------

Co-authored-by: david_smith <[email protected]>
  • Loading branch information
zero-to-prod and david_smith authored Nov 12, 2024
1 parent 1aa3e93 commit 3713df8
Show file tree
Hide file tree
Showing 21 changed files with 398 additions and 4 deletions.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class DataModelHelper
- [pregReplace](#pregreplace): Perform a regular expression search and replace.
- [pregMatch](#pregmatch): Perform a regular expression match.
- [isUrl](#isurl): Validates a url.
- [isEmail](#isemail): Validates an email.

### `mapOf`

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

### `isUrl`

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

```php
class User
Expand All @@ -486,7 +487,27 @@ class User
'cast' => [self::class, 'isUrl'],
'protocols' => ['http', 'udp'], // Optional. Defaults to all.
'on_fail' => [MyAction::class, 'method'], // Optional. Invoked when validation fails.
'exception' => InvalidUrlException::class, // Optional. Throws an exception when not url.
'exception' => MyCustomException::class, // Optional. Throws an exception when not url.
'required' // Optional. Throws \Zerotoprod\DataModel\PropertyRequiredException::class
])]
public string $url;
}
```

### `isEmail`

Use `isEmail` to validate an email.

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

#[Describe([
'cast' => [self::class, 'isEmail'],
'on_fail' => [MyAction::class, 'method'], // Optional. Invoked when validation fails.
'exception' => MyCustomException::class, // Optional. Throws an exception when not url.
'required' // Optional. Throws \Zerotoprod\DataModel\PropertyRequiredException::class
])]
public string $url;
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"require": {
"php": ">=8.1.0",
"zero-to-prod/data-model": "^v81.0.0",
"zero-to-prod/validate-url": "^71.0"
"zero-to-prod/validate-url": "^71.0",
"zero-to-prod/validate-email": "^71.0"
},
"autoload": {
"psr-4": {
Expand Down
46 changes: 45 additions & 1 deletion src/DataModelHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use ReflectionAttribute;
use ReflectionProperty;
use Zerotoprod\DataModel\PropertyRequiredException;
use Zerotoprod\ValidateEmail\ValidateEmail;
use Zerotoprod\ValidateUrl\ValidateUrl;

/**
Expand Down Expand Up @@ -159,7 +160,7 @@ public static function pregMatch(mixed $value, array $context, ?ReflectionAttrib
* 'cast' => [self::class, 'isUrl'],
* 'protocols' => ['http', 'udp'], // Optional. Defaults to all.
* 'on_fail' => [MyAction::class, 'method'], // Optional. Invoked when validation fails.
* 'exception' => InvalidUrlException::class, // Optional. Throws an exception when not url.
* 'exception' => MyException::class, // Optional. Throws an exception when not url.
* ])]
* ```
*/
Expand Down Expand Up @@ -195,4 +196,47 @@ public static function isUrl(mixed $value, array $context, ?ReflectionAttribute

return $value;
}

/**
* Determine if a given value is a valid URL.
* ```
* #[Describe([
* '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.
* ])]
* ```
*/
public static function isEmail(mixed $value, array $context, ?ReflectionAttribute $Attribute, ReflectionProperty $Property): ?string
{
$args = $Attribute?->getArguments()[0];
if (!$value && $Property->getType()?->allowsNull()) {
return null;
}

if (!$value && in_array('required', $args, true)) {
throw new PropertyRequiredException("Property `\${$Property->getName()}` is required.");
}

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 (!ValidateEmail::isEmail($value)) {
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;
}
}
10 changes: 10 additions & 0 deletions tests/Unit/IsEmail/Callable/InvalidEmail/BadEmailException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Tests\Unit\IsEmail\Callable\InvalidEmail;

use RuntimeException;

class BadEmailException extends RuntimeException
{

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

namespace Tests\Unit\IsEmail\Callable\InvalidEmail;

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

class InvalidEmailTest extends TestCase
{
#[Test] public function invalid_email(): void
{
$this->expectException(BadEmailException::class);
User::from([
User::email => 'invalid email',
]);
}
}
26 changes: 26 additions & 0 deletions tests/Unit/IsEmail/Callable/InvalidEmail/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Tests\Unit\IsEmail\Callable\InvalidEmail;

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

class User
{
use DataModel;
use DataModelHelper;

public const email = 'email';

#[Describe([
'cast' => [self::class, 'isEmail'],
'on_fail' => [self::class, 'failed'],
])]
public string $email;

public static function failed(): string
{
throw new BadEmailException();
}
}
10 changes: 10 additions & 0 deletions tests/Unit/IsEmail/Callable/NotString/BadEmailException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Tests\Unit\IsEmail\Callable\NotString;

use RuntimeException;

class BadEmailException extends RuntimeException
{

}
17 changes: 17 additions & 0 deletions tests/Unit/IsEmail/Callable/NotString/NotEmailStringTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Tests\Unit\IsEmail\Callable\NotString;

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

class NotEmailStringTest extends TestCase
{
#[Test] public function invalid_email(): void
{
$this->expectException(BadEmailException::class);
User::from([
User::email => 1,
]);
}
}
26 changes: 26 additions & 0 deletions tests/Unit/IsEmail/Callable/NotString/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Tests\Unit\IsEmail\Callable\NotString;

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

class User
{
use DataModel;
use DataModelHelper;

public const email = 'email';

#[Describe([
'cast' => [self::class, 'isEmail'],
'on_fail' => [self::class, 'failed'],
])]
public string $email;

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

namespace Tests\Unit\IsEmail\Exception\InvalidUrl;

use RuntimeException;

class BadEmailException extends RuntimeException
{

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

namespace Tests\Unit\IsEmail\Exception\InvalidUrl;

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

class CustomExceptionTest extends TestCase
{
#[Test] public function invalid_email(): void
{
$this->expectException(BadEmailException::class);
User::from([
User::email => 'invalid email',
]);
}
}
21 changes: 21 additions & 0 deletions tests/Unit/IsEmail/Exception/InvalidUrl/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Tests\Unit\IsEmail\Exception\InvalidUrl;

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

class User
{
use DataModel;
use DataModelHelper;

public const email = 'email';

#[Describe([
'cast' => [self::class, 'isEmail'],
'exception' => BadEmailException::class
])]
public string $email;
}
10 changes: 10 additions & 0 deletions tests/Unit/IsEmail/Exception/NotString/BadEmailException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Tests\Unit\IsEmail\Exception\NotString;

use RuntimeException;

class BadEmailException extends RuntimeException
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Tests\Unit\IsEmail\Exception\NotString;

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

class NotStringEmailCustomExceptionTest extends TestCase
{
#[Test] public function not_string(): void
{
$this->expectException(BadEmailException::class);
User::from([
User::email => 1,
]);
}
}
21 changes: 21 additions & 0 deletions tests/Unit/IsEmail/Exception/NotString/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Tests\Unit\IsEmail\Exception\NotString;

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

class User
{
use DataModel;
use DataModelHelper;

public const email = 'email';

#[Describe([
'cast' => [self::class, 'isEmail'],
'exception' => BadEmailException::class
])]
public string $email;
}
16 changes: 16 additions & 0 deletions tests/Unit/IsEmail/NullUrl/NullUrlTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Tests\Unit\IsEmail\NullUrl;

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

class NullUrlTest extends TestCase
{
#[Test] public function null_email(): void
{
$User = User::from();

self::assertNull($User->email);
}
}
20 changes: 20 additions & 0 deletions tests/Unit/IsEmail/NullUrl/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Tests\Unit\IsEmail\NullUrl;

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

class User
{
use DataModel;
use DataModelHelper;

public const email = 'email';

#[Describe([
'cast' => [self::class, 'isEmail']
])]
public ?string $email;
}
Loading

0 comments on commit 3713df8

Please sign in to comment.