Skip to content

Commit

Permalink
Add option to throw custom exceptions in guards (#64)
Browse files Browse the repository at this point in the history
* Add custom exceptions

* Extend readme

* Extend docs

* Updated changelog and upgrade

* Add tests for custom exceptions for id

* Add coverage for custom exceptions for list functions

---------

Co-authored-by: Christian Kolb <[email protected]>
  • Loading branch information
christian-kolb and Christian Kolb authored Jun 8, 2024
1 parent 98ec360 commit e3f5230
Show file tree
Hide file tree
Showing 20 changed files with 643 additions and 50 deletions.
3 changes: 3 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,8 @@

// Nullable types should be explicit even with default values
'nullable_type_declaration_for_default_null_value' => false,

// Throw in a single line is worse to read when using ternary operator
'single_line_throw' => false,
])
->setFinder($finder);
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 1.1.0

- Added option to supply a custom exception to the guard methods of `Id` and `IdList`. This allows for more specific exceptions when the guard fails.

Example of using the new (optional) parameter:
```php
$requestingUser->userId->mustNotBeEqualTo(
$command->targetUserId,
static fn () => new Exception\UserCanNotTargetItself(),
);
```

## 1.0.0

Reached stability after 2 years of usage in multiple scaled production systems.
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,19 @@ if ($userId->isEqualTo($command->userId)) {
}
```

Guard against invalid usages:
```php
$requestingUser->userId->mustNotBeEqualTo($command->targetUserId);
```

Or with a custom exception:
```php
$requestingUser->userId->mustNotBeEqualTo(
$command->targetUserId,
static fn () => new Exception\UserCanNotTargetItself(),
);
```

### Symfony serializer

If you're injecting the `SerializerInterface` directly, there is nothing to do. The normalizer for the id is automatically registered.
Expand Down Expand Up @@ -202,8 +211,17 @@ if ($idsOfEnabledUsers->contains($command->userId)) {
}
```

Guard against invalid usages:
```php
$idsOfEnabledUsers->mustContainId($command->targetUserId);
```

Or with custom exception:
```php
$idsOfEnabledUsers->mustContain($command->targetUserId);
$idsOfEnabledUsers->mustContainId(
$command->targetUserId,
static fn () => new Exception\UserIsNotEnabled(),
);
```

### Symfony serializer
Expand Down
4 changes: 4 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Upgrade guide

## From 1.0.0 to 1.1.0

Nothing to do.

## From 0.15.* to 1.0.0

Nothing to do.
Expand Down
34 changes: 26 additions & 8 deletions src/ValueObject/Id.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,37 @@ public function isNotEqualTo(self $id): bool

// Guards

/** @throws Exception\IdNotEqual */
public function mustBeEqualTo(self $id): void
{
/**
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdNotEqual
*/
public function mustBeEqualTo(
self $id,
?callable $exception = null,
): void {
if ($this->isNotEqualTo($id)) {
throw new Exception\IdNotEqual($this, $id);
throw $exception !== null
? $exception()
: new Exception\IdNotEqual($this, $id);
}
}

/** @throws Exception\IdEqual */
public function mustNotBeEqualTo(self $id): void
{
/**
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdEqual
*/
public function mustNotBeEqualTo(
self $id,
?callable $exception = null,
): void {
if ($this->isEqualTo($id)) {
throw new Exception\IdEqual($this, $id);
throw $exception !== null
? $exception()
: new Exception\IdEqual($this, $id);
}
}
}
168 changes: 127 additions & 41 deletions src/ValueObject/IdList.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

// -- Construction

/** @param array<array-key, T> $ids */
/**
* @param array<array-key, T> $ids
*/
final public function __construct(
array $ids,
) {
Expand Down Expand Up @@ -412,90 +414,174 @@ public function idsAsStringList(): array
// -- Guards

/**
* @param T $id
* @param T $id
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListDoesNotContainId
*/
public function mustContainId(Id $id): void
{
public function mustContainId(
Id $id,
?callable $exception = null,
): void {
if ($this->notContainsId($id)) {
throw new Exception\IdListDoesNotContainId($id);
throw $exception !== null
? $exception()
: new Exception\IdListDoesNotContainId($id);
}
}

/**
* @param T $id
* @param T $id
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListDoesContainId
*/
public function mustNotContainId(Id $id): void
{
public function mustNotContainId(
Id $id,
?callable $exception = null,
): void {
if ($this->containsId($id)) {
throw new Exception\IdListDoesContainId($id);
throw $exception !== null
? $exception()
: new Exception\IdListDoesContainId($id);
}
}

/** @throws Exception\IdListDoesNotContainEveryId */
public function mustContainEveryId(self $idList): void
{
/**
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListDoesNotContainEveryId
*/
public function mustContainEveryId(
self $idList,
?callable $exception = null,
): void {
if (!$this->containsEveryId($idList)) {
throw new Exception\IdListDoesNotContainEveryId();
throw $exception !== null
? $exception()
: new Exception\IdListDoesNotContainEveryId();
}
}

/** @throws Exception\IdListDoesContainEveryId */
public function mustNotContainEveryId(self $idList): void
{
/**
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListDoesContainEveryId
*/
public function mustNotContainEveryId(
self $idList,
?callable $exception = null,
): void {
if (!$this->notContainsEveryId($idList)) {
throw new Exception\IdListDoesContainEveryId();
throw $exception !== null
? $exception()
: new Exception\IdListDoesContainEveryId();
}
}

/** @throws Exception\IdListDoesNotContainSomeIds */
public function mustContainSomeIds(self $idList): void
{
/**
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListDoesNotContainSomeIds
*/
public function mustContainSomeIds(
self $idList,
?callable $exception = null,
): void {
if (!$this->containsSomeIds($idList)) {
throw new Exception\IdListDoesNotContainSomeIds();
throw $exception !== null
? $exception()
: new Exception\IdListDoesNotContainSomeIds();
}
}

/** @throws Exception\IdListDoesContainNoneIds */
public function mustContainNoneIds(self $idList): void
{
/**
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListDoesContainNoneIds
*/
public function mustContainNoneIds(
self $idList,
?callable $exception = null,
): void {
if (!$this->containsNoneIds($idList)) {
throw new Exception\IdListDoesContainNoneIds();
throw $exception !== null
? $exception()
: new Exception\IdListDoesContainNoneIds();
}
}

/** @throws Exception\IdListIsNotEmpty */
public function mustBeEmpty(): void
{
/**
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListIsNotEmpty
*/
public function mustBeEmpty(
?callable $exception = null,
): void {
if ($this->isNotEmpty()) {
throw new Exception\IdListIsNotEmpty();
throw $exception !== null
? $exception()
: new Exception\IdListIsNotEmpty();
}
}

/** @throws Exception\IdListIsEmpty */
public function mustNotBeEmpty(): void
{
/**
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListIsEmpty
*/
public function mustNotBeEmpty(
?callable $exception = null,
): void {
if ($this->isEmpty()) {
throw new Exception\IdListIsEmpty();
throw $exception !== null
? $exception()
: new Exception\IdListIsEmpty();
}
}

/** @param static $idList */
public function mustBeEqualTo(self $idList): void
{
/**
* @param static $idList
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListsMustBeEqual
*/
public function mustBeEqualTo(
self $idList,
?callable $exception = null,
): void {
if ($this->isNotEqualTo($idList)) {
throw new Exception\IdListsMustBeEqual();
throw $exception !== null
? $exception()
: new Exception\IdListsMustBeEqual();
}
}

/** @param static $idList */
public function mustNotBeEqualTo(self $idList): void
{
/**
* @param static $idList
* @param ?callable(): \Throwable $exception
*
* @throws \Throwable
* @throws Exception\IdListsMustNotBeEqual
*/
public function mustNotBeEqualTo(
self $idList,
?callable $exception = null,
): void {
if ($this->isEqualTo($idList)) {
throw new Exception\IdListsMustNotBeEqual();
throw $exception !== null
? $exception()
: new Exception\IdListsMustNotBeEqual();
}
}

Expand Down
9 changes: 9 additions & 0 deletions tests/Test/Exception/AllUsersMustBeAbleToPerformAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\Ids\Test\Exception;

final class AllUsersMustBeAbleToPerformAction extends \InvalidArgumentException
{
}
9 changes: 9 additions & 0 deletions tests/Test/Exception/ListOfUsersHasNotChanged.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\Ids\Test\Exception;

final class ListOfUsersHasNotChanged extends \InvalidArgumentException
{
}
9 changes: 9 additions & 0 deletions tests/Test/Exception/NoUserCanPerformAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\Ids\Test\Exception;

final class NoUserCanPerformAction extends \InvalidArgumentException
{
}
9 changes: 9 additions & 0 deletions tests/Test/Exception/NotAllUsersAreDisabled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\Ids\Test\Exception;

final class NotAllUsersAreDisabled extends \InvalidArgumentException
{
}
9 changes: 9 additions & 0 deletions tests/Test/Exception/NotAllUsersAreEnabled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace DigitalCraftsman\Ids\Test\Exception;

final class NotAllUsersAreEnabled extends \InvalidArgumentException
{
}
Loading

0 comments on commit e3f5230

Please sign in to comment.