Skip to content

Commit

Permalink
Switch to __invoke for command handlers and query handlers (#44)
Browse files Browse the repository at this point in the history
* Switch to invoke for handlers

* Extend changelog

---------

Co-authored-by: Christian Kolb <info@digital-craftsman.de>
  • Loading branch information
christian-kolb and Christian Kolb authored Jul 7, 2023
1 parent cbde0e1 commit f164af7
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 39 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.12.0

- **[Breaking change](./UPGRADE.md#switched-handler-methods)**: Switched from `handle` to `__invoke` method for `CommandHandlerInterface` and `QueryHandlerInterface`. This way the specific command or query can be type hinted in the method signature.

## 0.11.0

- **[Breaking change](./UPGRADE.md#upgrade-to-at-least-php-82)**: Dropped support for PHP 8.1.
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Install package through composer:
composer require digital-craftsman/cqrs
```

> ⚠️ This bundle can be used (and is being used) in production, but hasn't reached version 1.0 yet. Therefore, there will be breaking changes between minor versions. I'd recommend that you require the bundle only with the current minor version like `composer require digital-craftsman/cqrs:0.11.*`. Breaking changes are described in the releases and [the changelog](./CHANGELOG.md). Updates are described in the [upgrade guide](./UPGRADE.md).
> ⚠️ This bundle can be used (and is being used) in production, but hasn't reached version 1.0 yet. Therefore, there will be breaking changes between minor versions. I'd recommend that you require the bundle only with the current minor version like `composer require digital-craftsman/cqrs:0.12.*`. Breaking changes are described in the releases and [the changelog](./CHANGELOG.md). Updates are described in the [upgrade guide](./UPGRADE.md).
Then add the following `cqrs.php` file to your `config/packages` and replace it with your instances of the interfaces:

Expand Down Expand Up @@ -158,8 +158,7 @@ final readonly class CreateNewsArticleCommandHandler implements CommandHandlerIn
) {
}

/** @param CreateProductNewsArticleCommand $command */
public function handle(Command $command): void
public function __invoke(CreateProductNewsArticleCommand $command): void
{
$commandExecutedAt = $this->clock->now();

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

## From 0.11.* to 0.12.0

### Switched handler methods

Switched from `handle` to `__invoke` method for `CommandHandler` and `QueryHandler`.

You need to update the methods in your command and query handlers like the following:

Before:

```php
final readonly class CreateNewsArticleCommandHandler implements CommandHandlerInterface
{
/** @param CreateNewsArticleCommand $command */
public function handle(Command $command): void
{
$newsArticle = new NewsArticle(
NewsArticleId::generateRandom(),
$command->userId,
$command->title,
$command->content,
$command->isPublished,
);
...
```

After:

```php
final readonly class CreateNewsArticleCommandHandler implements CommandHandlerInterface
{
public function __invoke(CreateNewsArticleCommand $command): void
{
$newsArticle = new NewsArticle(
NewsArticleId::generateRandom(),
$command->userId,
$command->title,
$command->content,
$command->isPublished,
);
...
```

## From 0.10.* to 0.11.0

### Upgrade to at least PHP 8.2
Expand Down
16 changes: 8 additions & 8 deletions docs/examples/handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

**Interfaces**

The interfaces are simple marker interfaces.

```php
/** @method void __invoke(Command $command) */
interface CommandHandlerInterface
{
public function handle(Command $command): void;
}
```

```php
/** @method void __invoke(Query $query) */
interface QueryHandlerInterface
{
public function handle(Query $query): mixed;
}
```

Expand All @@ -32,8 +34,7 @@ final class CreateUserAccountCommandHandler implements CommandHandlerInterface
) {
}

/** @param CreateUserAccountCommand $command */
public function handle(Command $command): void
public function __invoke(CreateUserAccountCommand $command): void
{
$this->requestingUserMustBeAdmin($command);

Expand Down Expand Up @@ -63,8 +64,7 @@ final readonly class GetUserQueryHandler implements QueryHandlerInterface
) {
}

/** @param GetUserQuery $query */
public function handle(Query $query): UserReadModel
public function __invoke(GetUserQuery $query): ReadModel\User
{
$this->requestingUserMustBeAdmin($query);

Expand All @@ -73,14 +73,14 @@ final readonly class GetUserQueryHandler implements QueryHandlerInterface

...

private function getTargetUser(GetUserQuery $query): UserReadModel
private function getTargetUser(GetUserQuery $query): ReadModel\User
{
$targetUser = $this->userRepository->findOneById($query->targetUserId);
if ($targetUser === null) {
throw new TargetUserNotFound();
}

return new UserReadModel(
return new ReadModel\User(
$targetUser->userId,
$targetUser->name,
$targetUser->emailAddress,
Expand Down
2 changes: 1 addition & 1 deletion src/Command/CommandHandlerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace DigitalCraftsman\CQRS\Command;

/** @method void __invoke(Command $command) */
interface CommandHandlerInterface
{
public function handle(Command $command): void;
}
3 changes: 2 additions & 1 deletion src/Controller/CommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ public function handle(
// -- Trigger command through command handler
/** @psalm-suppress PossiblyInvalidArgument */
$commandHandler = $this->serviceMap->getCommandHandler($configuration->handlerClass);
$commandHandler->handle($command);
/** @psalm-suppress InvalidFunctionCall */
$commandHandler($command);

$handlerWrapperClassesForThenStep = HandlerWrapperStep::then($handlerWrapperClasses);
foreach ($handlerWrapperClassesForThenStep->orderedHandlerWrapperClasses as $handlerWrapperClass => $parameters) {
Expand Down
3 changes: 2 additions & 1 deletion src/Controller/QueryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ public function handle(
$result = null;

try {
$result = $queryHandler->handle($query);
/** @psalm-suppress InvalidFunctionCall */
$result = $queryHandler($query);

$handlerWrapperClassesForThenStep = HandlerWrapperStep::then($handlerWrapperClasses);
foreach ($handlerWrapperClassesForThenStep->orderedHandlerWrapperClasses as $handlerWrapperClass => $parameters) {
Expand Down
2 changes: 1 addition & 1 deletion src/Query/QueryHandlerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace DigitalCraftsman\CQRS\Query;

/** @method mixed __invoke(Query $query) */
interface QueryHandlerInterface
{
public function handle(Query $query): mixed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@

namespace DigitalCraftsman\CQRS\Test\Domain\News\WriteSide\CreateNewsArticle;

use DigitalCraftsman\CQRS\Command\Command;
use DigitalCraftsman\CQRS\Command\CommandHandlerInterface;
use DigitalCraftsman\CQRS\Test\Entity\NewsArticle;
use DigitalCraftsman\CQRS\Test\Repository\NewsArticleInMemoryRepository;
use DigitalCraftsman\CQRS\Test\ValueObject\NewsArticleId;

final class CreateNewsArticleCommandHandler implements CommandHandlerInterface
final readonly class CreateNewsArticleCommandHandler implements CommandHandlerInterface
{
public function __construct(
private NewsArticleInMemoryRepository $newsArticleInMemoryRepository,
) {
}

/** @param CreateNewsArticleCommand $command */
public function handle(Command $command): void
public function __invoke(CreateNewsArticleCommand $command): void
{
$newsArticle = new NewsArticle(
NewsArticleId::generateRandom(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

namespace DigitalCraftsman\CQRS\Test\Domain\News\WriteSide\CreateNewsArticle;

use DigitalCraftsman\CQRS\Command\Command;
use DigitalCraftsman\CQRS\Command\CommandHandlerInterface;
use DigitalCraftsman\CQRS\Test\Domain\News\WriteSide\CreateNewsArticle\Exception\NewsArticleAlreadyExists;

final class FailingCreateNewsArticleCommandHandler implements CommandHandlerInterface
final readonly class FailingCreateNewsArticleCommandHandler implements CommandHandlerInterface
{
/** @param CreateNewsArticleCommand $command */
public function handle(Command $command): void
public function __invoke(CreateNewsArticleCommand $command): void
{
// Some logic that validates it

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@

namespace DigitalCraftsman\CQRS\Test\Domain\Tasks\ReadSide\GetTasks;

use DigitalCraftsman\CQRS\Query\Query;
use DigitalCraftsman\CQRS\Query\QueryHandlerInterface;
use DigitalCraftsman\CQRS\Test\Domain\Tasks\ReadSide\GetTasks\Exception\TasksNotAccessible;

/** @psalm-immutable */
final class FailingGetTasksQueryHandler implements QueryHandlerInterface
final readonly class FailingGetTasksQueryHandler implements QueryHandlerInterface
{
/** @param GetTasksQuery $query */
public function handle(Query $query): array
public function __invoke(GetTasksQuery $query): array
{
throw new TasksNotAccessible();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@

namespace DigitalCraftsman\CQRS\Test\Domain\Tasks\ReadSide\GetTasks;

use DigitalCraftsman\CQRS\Query\Query;
use DigitalCraftsman\CQRS\Query\QueryHandlerInterface;
use DigitalCraftsman\CQRS\Test\Repository\TasksInMemoryRepository;

/** @psalm-immutable */
final class GetTasksQueryHandler implements QueryHandlerInterface
final readonly class GetTasksQueryHandler implements QueryHandlerInterface
{
public function __construct(
private TasksInMemoryRepository $tasksInMemoryRepository,
) {
}

/** @param GetTasksQuery $query */
public function handle(Query $query): array
public function __invoke(GetTasksQuery $query): array
{
return $this->tasksInMemoryRepository->findAll();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@

namespace DigitalCraftsman\CQRS\Test\Domain\Tasks\WriteSide\CreateTask;

use DigitalCraftsman\CQRS\Command\Command;
use DigitalCraftsman\CQRS\Command\CommandHandlerInterface;

final class CreateTaskCommandHandler implements CommandHandlerInterface
{
/** @param CreateTaskCommand $command */
public function handle(Command $command): void
public function __invoke(CreateTaskCommand $command): void
{
// Create new task and store it ...
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

namespace DigitalCraftsman\CQRS\Test\Domain\Tasks\WriteSide\MarkTaskAsAccepted;

use DigitalCraftsman\CQRS\Command\Command;
use DigitalCraftsman\CQRS\Command\CommandHandlerInterface;
use DigitalCraftsman\CQRS\Test\Domain\Tasks\WriteSide\MarkTaskAsAccepted\Exception\TaskAlreadyAccepted;

final class MarkTaskAsAcceptedCommandHandler implements CommandHandlerInterface
final readonly class MarkTaskAsAcceptedCommandHandler implements CommandHandlerInterface
{
public function handle(Command $command): void
public function handle(MarkTaskAsAcceptedCommand $command): void
{
// Task was already marked as accepted
throw new TaskAlreadyAccepted();
Expand Down

0 comments on commit f164af7

Please sign in to comment.