Skip to content
Merged
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
8 changes: 4 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 4 additions & 18 deletions docs/v1/configuration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Configuration Reference

`Webware\Message\ConfigProvider` registers four sections.
`Webware\Message\ConfigProvider` registers three sections.

## Dependencies

Expand All @@ -13,6 +13,9 @@
'factories' => [
MessageMiddleware::class => MessageMiddlewareFactory::class,
],
'invokables' => [
NotificationMiddleware::class => NotificationMiddleware::class,
],
]
```

Expand Down Expand Up @@ -49,20 +52,3 @@ through the helper.
],
]
```

## Message Templates

```php
// getMessageTemplates()
[
SystemMessengerInterface::MESSAGE_TEMPLATES => [
// YourCommand::class => [
// NotificationCapableInterface::MESSAGE_SUCCESS => 'Your success message',
// NotificationCapableInterface::MESSAGE_FAILURE => 'Your failure message',
// ],
],
]
```

Applications may map command classes to success/failure template strings,
keyed under `SystemMessengerInterface::MESSAGE_TEMPLATES` (`message_templates`).
55 changes: 55 additions & 0 deletions docs/v1/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,58 @@ use Webware\Message\SystemMessengerInterface;

$messenger = $request->getAttribute(SystemMessengerInterface::class);
```

## Notification middleware

`Webware\Message\Middleware\NotificationMiddleware` turns a completed command
result into a flash notification. It reads the message-bus `CommandResult`
request attribute and pushes a success or warning message when the dispatched
command implements `NotificationCapableInterface`.

### Wiring

The middleware must run after `MessageMiddleware` (so the
`SystemMessengerInterface` request attribute exists) and after the middleware
that dispatches the command and stores the `CommandResult` attribute:

```php
// config/pipeline.php
$app->pipe(\Mezzio\Session\SessionMiddleware::class);
$app->pipe(\Webware\Message\Middleware\MessageMiddleware::class);
// ... routing and command-processing middleware ...
$app->pipe(\Webware\Message\Middleware\NotificationMiddleware::class);
```

`NotificationMiddleware` is registered by `ConfigProvider` as an invokable and
requires no constructor dependencies.

### Behavior

For each request, the middleware:

1. Reads the `CommandResult` and `SystemMessengerInterface` request attributes.
2. Returns immediately when either attribute is missing.
3. Reads the dispatched command via `CommandResult::getCommand()`.
4. Returns immediately when the command does not implement
`NotificationCapableInterface`.
5. Maps the result status to a flash message:
- `MessageStatus::Success` → `$successMessage` (success level)
- `MessageStatus::Failure` → `$failureMessage` (warning level)
6. Delegates to the next handler.

### NotificationCapableInterface

Commands that want a flash notification declare their user-facing text by
implementing `NotificationCapableInterface`:

```php
use Webware\Message\NotificationCapableInterface;
use Webware\MessageBus\Command\CommandInterface;

final readonly class SaveRoleCommand implements CommandInterface, NotificationCapableInterface
{
public string $successMessage { get => 'Role saved.'; }

public string $failureMessage { get => 'Role could not be saved.'; }
}
```
25 changes: 7 additions & 18 deletions src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,14 @@
public function getDependencies(): array
{
return [
'aliases' => [
'aliases' => [
SystemMessengerInterface::class => SystemMessenger::class,
],
'factories' => [
'factories' => [
Middleware\MessageMiddleware::class => Middleware\MessageMiddlewareFactory::class,
],
];
}

/** @return array<string, mixed> */
public function getMessageTemplates(): array
{
return [
SystemMessengerInterface::MESSAGE_TEMPLATES => [
// YourCommand::class => [
// NotificationCapableInterface::MESSAGE_SUCCESS => 'Your success message',
// NotificationCapableInterface::MESSAGE_FAILURE => 'Your failure message',
// ],
'invokables' => [
Middleware\NotificationMiddleware::class => Middleware\NotificationMiddleware::class,
],
];
}
Expand Down Expand Up @@ -71,10 +61,9 @@ public function getViewHelpers(): array
public function __invoke(): array
{
return [
'dependencies' => $this->getDependencies(),
'templates' => $this->getTemplates(),
'view_helpers' => $this->getViewHelpers(),
SystemMessengerInterface::class => $this->getMessageTemplates(),
'dependencies' => $this->getDependencies(),
'templates' => $this->getTemplates(),
'view_helpers' => $this->getViewHelpers(),
];
}
}
2 changes: 1 addition & 1 deletion src/MessageLevelCapableInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
declare(strict_types=1);

/**
* This file is part of the Webware Webware Message package.
* This file is part of the Webware Message package.
*
* Copyright (c) 2026 Joey Smith <jsmith@webinertia.net>
* and contributors.
Expand Down
68 changes: 68 additions & 0 deletions src/Middleware/NotificationMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

/**
* This file is part of the Webware Message package.
*
* Copyright (c) 2026 Joey Smith <jsmith@webinertia.net>
* and contributors.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Webware\Message\Middleware;

use Override;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Webware\Message\Exception\InvalidHopsValueException;
use Webware\Message\MessageLevel;
use Webware\Message\NotificationCapableInterface;
use Webware\Message\SystemMessengerInterface;
use Webware\MessageBus\Command\CommandResult;
use Webware\MessageBus\MessageStatus;

/**
* Translates a completed command result into a flash notification.
*/
final readonly class NotificationMiddleware implements MiddlewareInterface
{
/**
* @throws InvalidHopsValueException
*/
#[Override]
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
/** @var CommandResult|null $result */
$result = $request->getAttribute(CommandResult::class);

/** @var SystemMessengerInterface|null $messenger */
$messenger = $request->getAttribute(SystemMessengerInterface::class);

if (! $result instanceof CommandResult || ! $messenger instanceof SystemMessengerInterface) {
return $handler->handle($request);
}

$command = $result->getCommand();

if (! $command instanceof NotificationCapableInterface) {
return $handler->handle($request);
}

$isSuccess = $result->getStatus() === MessageStatus::Success;
$message = $isSuccess ? $command->successMessage : $command->failureMessage;
$level = $isSuccess ? MessageLevel::Success : MessageLevel::Warning;

$messenger->sendNow(
message: $message,
key : $level,
hops : 0,

Check warning on line 63 in src/Middleware/NotificationMiddleware.php

View workflow job for this annotation

GitHub Actions / qa / Mutation testing

Escaped Mutant for Mutator "DecrementInteger": @@ @@ $messenger->sendNow( message: $message, key : $level, - hops : 0, + hops : -1, ); return $handler->handle($request);

Check warning on line 63 in src/Middleware/NotificationMiddleware.php

View workflow job for this annotation

GitHub Actions / qa / Mutation testing

Escaped Mutant for Mutator "IncrementInteger": @@ @@ $messenger->sendNow( message: $message, key : $level, - hops : 0, + hops : 1, ); return $handler->handle($request);
);

return $handler->handle($request);
}
}
28 changes: 28 additions & 0 deletions src/NotificationCapableInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

/**
* This file is part of the Webware Message package.
*
* Copyright (c) 2026 Joey Smith <jsmith@webinertia.net>
* and contributors.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Webware\Message;

/**
* Implemented by messages that expose the user-facing notification text for
* their success and failure outcomes.
*
* @api
*/
interface NotificationCapableInterface
{
public string $successMessage { get; }

public string $failureMessage { get; }
}
3 changes: 1 addition & 2 deletions src/SystemMessengerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
*/
interface SystemMessengerInterface
{
public const string MESSAGE_TEMPLATES = 'message_templates';
public const string SESSION_KEY = self::class . '::SYSTEM_MESSENGER_NEXT';
public const string SESSION_KEY = self::class . '::SYSTEM_MESSENGER_NEXT';

public function addHop(): void;

Expand Down
34 changes: 10 additions & 24 deletions test/unit/ConfigProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use Webware\Message\ConfigProvider;
use Webware\Message\Middleware\MessageMiddleware;
use Webware\Message\Middleware\MessageMiddlewareFactory;
use Webware\Message\Middleware\NotificationMiddleware;
use Webware\Message\SystemMessenger;
use Webware\Message\SystemMessengerInterface;
use Webware\Message\View\Helper\SystemMessenger as SystemMessengerHelper;
Expand All @@ -30,7 +31,6 @@

#[CoversClass(ConfigProvider::class)]
#[CoversMethod(ConfigProvider::class, 'getDependencies')]
#[CoversMethod(ConfigProvider::class, 'getMessageTemplates')]
#[CoversMethod(ConfigProvider::class, 'getTemplates')]
#[CoversMethod(ConfigProvider::class, 'getViewHelpers')]
#[CoversMethod(ConfigProvider::class, '__invoke')]
Expand All @@ -40,39 +40,26 @@ final class ConfigProviderTest extends TestCase
* @throws \PHPUnit\Exception
*/
#[Test]
public function getDependenciesReturnsMessengerAliasAndMiddlewareFactory(): void
public function getDependenciesReturnsAliasesFactoriesAndInvokables(): void
{
$provider = new ConfigProvider();

self::assertSame(
[
'aliases' => [
'aliases' => [
SystemMessengerInterface::class => SystemMessenger::class,
],
'factories' => [
'factories' => [
MessageMiddleware::class => MessageMiddlewareFactory::class,
],
'invokables' => [
NotificationMiddleware::class => NotificationMiddleware::class,
],
],
$provider->getDependencies(),
);
}

/**
* @throws \PHPUnit\Exception
*/
#[Test]
public function getMessageTemplatesReturnsEmptyTemplateMapByDefault(): void
{
$provider = new ConfigProvider();

self::assertSame(
[
SystemMessengerInterface::MESSAGE_TEMPLATES => [],
],
$provider->getMessageTemplates(),
);
}

/**
* @throws \PHPUnit\Exception
*/
Expand Down Expand Up @@ -126,10 +113,9 @@ public function invokeMergesAllConfigurationSections(): void

self::assertSame(
[
'dependencies' => $provider->getDependencies(),
'templates' => $provider->getTemplates(),
'view_helpers' => $provider->getViewHelpers(),
SystemMessengerInterface::class => $provider->getMessageTemplates(),
'dependencies' => $provider->getDependencies(),
'templates' => $provider->getTemplates(),
'view_helpers' => $provider->getViewHelpers(),
],
$config,
);
Expand Down
Loading
Loading